From 7cf8a5c302735d193dcf901b87e03e324903c632 Mon Sep 17 00:00:00 2001
From: Simon Riggs <simon@2ndQuadrant.com>
Date: Mon, 2 Apr 2018 21:34:15 +0100
Subject: [PATCH] Revert "Modified files for MERGE"

This reverts commit 354f13855e6381d288dfaa52bcd4f2cb0fd4a5eb.
---
 contrib/test_decoding/expected/ddl.out    |  46 ---
 contrib/test_decoding/sql/ddl.sql         |  16 -
 doc/src/sgml/libpq.sgml                   |   8 +-
 doc/src/sgml/mvcc.sgml                    |  28 +-
 doc/src/sgml/plpgsql.sgml                 |   3 +-
 doc/src/sgml/ref/allfiles.sgml            |   1 -
 doc/src/sgml/ref/create_policy.sgml       |   7 -
 doc/src/sgml/ref/insert.sgml              |  11 +-
 doc/src/sgml/reference.sgml               |   1 -
 doc/src/sgml/trigger.sgml                 |  20 --
 src/backend/access/heap/heapam.c          |  28 +-
 src/backend/catalog/sql_features.txt      |   6 +-
 src/backend/commands/explain.c            |  33 --
 src/backend/commands/prepare.c            |   1 -
 src/backend/commands/trigger.c            | 156 ++-------
 src/backend/executor/Makefile             |   2 +-
 src/backend/executor/README               |  10 -
 src/backend/executor/execMain.c           |  17 -
 src/backend/executor/execPartition.c      | 116 -------
 src/backend/executor/execReplication.c    |   4 +-
 src/backend/executor/nodeModifyTable.c    | 384 +++-------------------
 src/backend/executor/spi.c                |   3 -
 src/backend/nodes/copyfuncs.c             |  40 ---
 src/backend/nodes/equalfuncs.c            |  32 --
 src/backend/nodes/nodeFuncs.c             |  48 +--
 src/backend/nodes/outfuncs.c              |  25 --
 src/backend/nodes/readfuncs.c             |   6 -
 src/backend/optimizer/plan/createplan.c   |  22 +-
 src/backend/optimizer/plan/planner.c      |  29 +-
 src/backend/optimizer/plan/setrefs.c      |  54 ---
 src/backend/optimizer/prep/preptlist.c    |  41 ---
 src/backend/optimizer/util/pathnode.c     |  11 +-
 src/backend/optimizer/util/plancat.c      |   4 -
 src/backend/parser/Makefile               |   2 +-
 src/backend/parser/analyze.c              |  18 +-
 src/backend/parser/gram.y                 | 158 +--------
 src/backend/parser/parse_agg.c            |  10 -
 src/backend/parser/parse_clause.c         |  45 +--
 src/backend/parser/parse_collate.c        |   1 -
 src/backend/parser/parse_expr.c           |   3 -
 src/backend/parser/parse_func.c           |   3 -
 src/backend/parser/parse_relation.c       |  10 -
 src/backend/rewrite/rewriteHandler.c      | 117 +------
 src/backend/rewrite/rowsecurity.c         |  97 ------
 src/backend/tcop/pquery.c                 |   5 -
 src/backend/tcop/utility.c                |  16 -
 src/include/access/heapam.h               |  13 +-
 src/include/commands/trigger.h            |   6 +-
 src/include/executor/execPartition.h      |   1 -
 src/include/executor/instrument.h         |   7 +-
 src/include/executor/nodeModifyTable.h    |  21 --
 src/include/executor/spi.h                |   1 -
 src/include/nodes/execnodes.h             |  65 +---
 src/include/nodes/nodes.h                 |   6 +-
 src/include/nodes/parsenodes.h            |  37 +--
 src/include/nodes/plannodes.h             |   8 +-
 src/include/nodes/relation.h              |   7 +-
 src/include/optimizer/pathnode.h          |   7 +-
 src/include/parser/analyze.h              |   5 -
 src/include/parser/kwlist.h               |   2 -
 src/include/parser/parse_clause.h         |   5 +-
 src/include/parser/parse_node.h           |   5 +-
 src/include/rewrite/rewriteHandler.h      |   1 -
 src/interfaces/libpq/fe-exec.c            |   9 +-
 src/pl/plpgsql/src/pl_exec.c              |   5 +-
 src/pl/plpgsql/src/pl_gram.y              |   8 -
 src/pl/plpgsql/src/pl_scanner.c           |   1 -
 src/pl/plpgsql/src/plpgsql.h              |   4 +-
 src/test/isolation/isolation_schedule     |   4 -
 src/test/regress/expected/identity.out    |  55 ----
 src/test/regress/expected/privileges.out  |  98 ------
 src/test/regress/expected/rowsecurity.out | 182 ----------
 src/test/regress/expected/rules.out       |  31 --
 src/test/regress/expected/triggers.out    |  48 ---
 src/test/regress/parallel_schedule        |   2 +-
 src/test/regress/serial_schedule          |   1 -
 src/test/regress/sql/identity.sql         |  45 ---
 src/test/regress/sql/privileges.sql       | 108 ------
 src/test/regress/sql/rowsecurity.sql      | 156 ---------
 src/test/regress/sql/rules.sql            |  33 --
 src/test/regress/sql/triggers.sql         |  47 ---
 src/tools/pgindent/typedefs.list          |   3 -
 82 files changed, 165 insertions(+), 2570 deletions(-)

diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 79c359d6e3..b7c76469fc 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -192,52 +192,6 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
  COMMIT
 (33 rows)
 
--- MERGE support
-BEGIN;
-MERGE INTO replication_example t
-	USING (SELECT i as id, i as data, i as num FROM generate_series(-20, 5) i) s
-	ON t.id = s.id
-	WHEN MATCHED AND t.id < 0 THEN
-		UPDATE SET somenum = somenum + 1
-	WHEN MATCHED AND t.id >= 0 THEN
-		DELETE
-	WHEN NOT MATCHED THEN
-		INSERT VALUES (s.*);
-COMMIT;
-/* display results */
-SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
-                                                                       data                                                                       
---------------------------------------------------------------------------------------------------------------------------------------------------
- BEGIN
- table public.replication_example: INSERT: id[integer]:-20 somedata[integer]:-20 somenum[integer]:-20 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: INSERT: id[integer]:-19 somedata[integer]:-19 somenum[integer]:-19 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: INSERT: id[integer]:-18 somedata[integer]:-18 somenum[integer]:-18 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: INSERT: id[integer]:-17 somedata[integer]:-17 somenum[integer]:-17 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: INSERT: id[integer]:-16 somedata[integer]:-16 somenum[integer]:-16 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-15 somedata[integer]:-15 somenum[integer]:-14 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-14 somedata[integer]:-14 somenum[integer]:-13 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-13 somedata[integer]:-13 somenum[integer]:-12 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-12 somedata[integer]:-12 somenum[integer]:-11 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-11 somedata[integer]:-11 somenum[integer]:-10 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-10 somedata[integer]:-10 somenum[integer]:-9 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-9 somedata[integer]:-9 somenum[integer]:-8 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-8 somedata[integer]:-8 somenum[integer]:-7 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-7 somedata[integer]:-7 somenum[integer]:-6 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-6 somedata[integer]:-6 somenum[integer]:-5 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-5 somedata[integer]:-5 somenum[integer]:-4 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-4 somedata[integer]:-4 somenum[integer]:-3 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-3 somedata[integer]:-3 somenum[integer]:-2 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-2 somedata[integer]:-2 somenum[integer]:-1 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-1 somedata[integer]:-1 somenum[integer]:0 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: DELETE: id[integer]:0
- table public.replication_example: DELETE: id[integer]:1
- table public.replication_example: DELETE: id[integer]:2
- table public.replication_example: DELETE: id[integer]:3
- table public.replication_example: DELETE: id[integer]:4
- table public.replication_example: DELETE: id[integer]:5
- COMMIT
-(28 rows)
-
 CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
 INSERT INTO tr_unique(data) VALUES(10);
 ALTER TABLE tr_unique RENAME TO tr_pkey;
diff --git a/contrib/test_decoding/sql/ddl.sql b/contrib/test_decoding/sql/ddl.sql
index 0e608b252f..c4b10a4cf9 100644
--- a/contrib/test_decoding/sql/ddl.sql
+++ b/contrib/test_decoding/sql/ddl.sql
@@ -93,22 +93,6 @@ COMMIT;
 /* display results */
 SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
 
--- MERGE support
-BEGIN;
-MERGE INTO replication_example t
-	USING (SELECT i as id, i as data, i as num FROM generate_series(-20, 5) i) s
-	ON t.id = s.id
-	WHEN MATCHED AND t.id < 0 THEN
-		UPDATE SET somenum = somenum + 1
-	WHEN MATCHED AND t.id >= 0 THEN
-		DELETE
-	WHEN NOT MATCHED THEN
-		INSERT VALUES (s.*);
-COMMIT;
-
-/* display results */
-SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
-
 CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
 INSERT INTO tr_unique(data) VALUES(10);
 ALTER TABLE tr_unique RENAME TO tr_pkey;
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 8729ccd5c5..943adfef77 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -3917,11 +3917,9 @@ char *PQcmdTuples(PGresult *res);
        <structname>PGresult</structname>. This function can only be used following
        the execution of a <command>SELECT</command>, <command>CREATE TABLE AS</command>,
        <command>INSERT</command>, <command>UPDATE</command>, <command>DELETE</command>,
-       <command>MERGE</command>, <command>MOVE</command>, <command>FETCH</command>,
-       or <command>COPY</command> statement, or an <command>EXECUTE</command> of a
-       prepared query that contains an <command>INSERT</command>,
-       <command>UPDATE</command>, <command>DELETE</command>
-       or <command>MERGE</command> statement.
+       <command>MOVE</command>, <command>FETCH</command>, or <command>COPY</command> statement,
+       or an <command>EXECUTE</command> of a prepared query that contains an
+       <command>INSERT</command>, <command>UPDATE</command>, or <command>DELETE</command> statement.
        If the command that generated the <structname>PGresult</structname> was anything
        else, <function>PQcmdTuples</function> returns an empty string. The caller
        should not free the return value directly. It will be freed when
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 0e3e89af56..24613e3c75 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -422,31 +422,6 @@ COMMIT;
     <literal>11</literal>, which no longer matches the criteria.
    </para>
 
-   <para>
-    The <command>MERGE</command> allows the user to specify various combinations
-    of <command>INSERT</command>, <command>UPDATE</command> or
-    <command>DELETE</command> subcommands. A <command>MERGE</command> command
-    with both <command>INSERT</command> and <command>UPDATE</command>
-    subcommands looks similar to <command>INSERT</command> with an
-    <literal>ON CONFLICT DO UPDATE</literal> clause but does not guarantee
-    that either <command>INSERT</command> and <command>UPDATE</command> will occur.
-
-    If MERGE attempts an UPDATE or DELETE and the row is concurrently updated
-    but the join condition still passes for the current target and the current
-    source tuple, then MERGE will behave the same as the UPDATE or DELETE commands
-    and perform its action on the latest version of the row, using standard
-    EvalPlanQual. MERGE actions can be conditional, so conditions must be
-    re-evaluated on the latest row, starting from the first action.
-
-    On the other hand, if the row is concurrently updated or deleted so that
-    the join condition fails, then MERGE will execute a NOT MATCHED action, if it
-    exists and the AND WHEN qual evaluates to true.
-
-    If MERGE attempts an INSERT and a unique index is present and a duplicate
-    row is concurrently inserted then a uniqueness violation is raised. MERGE
-    does not attempt to avoid the ERROR by attempting an UPDATE.
-   </para>
-
    <para>
     Because Read Committed mode starts each command with a new snapshot
     that includes all transactions committed up to that instant,
@@ -925,8 +900,7 @@ ERROR:  could not serialize access due to read/write dependencies among transact
 
         <para>
          The commands <command>UPDATE</command>,
-         <command>DELETE</command>, <command>INSERT</command> and
-         <command>MERGE</command>
+         <command>DELETE</command>, and <command>INSERT</command>
          acquire this lock mode on the target table (in addition to
          <literal>ACCESS SHARE</literal> locks on any other referenced
          tables).  In general, this lock mode will be acquired by any
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 59f6112b07..5b2aac618e 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -1246,7 +1246,7 @@ EXECUTE format('SELECT count(*) FROM %I '
 </programlisting>
      Another restriction on parameter symbols is that they only work in
      <command>SELECT</command>, <command>INSERT</command>, <command>UPDATE</command>, and
-     <command>DELETE</command> and <command>MERGE</command> commands.  In other statement
+     <command>DELETE</command> commands.  In other statement
      types (generically called utility statements), you must insert
      values textually even if they are just data values.
     </para>
@@ -1529,7 +1529,6 @@ GET DIAGNOSTICS integer_var = ROW_COUNT;
           <listitem>
            <para>
             <command>UPDATE</command>, <command>INSERT</command>, and <command>DELETE</command>
-            and <command>MERGE</command>
             statements set <literal>FOUND</literal> true if at least one
             row is affected, false if no row is affected.
            </para>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 4e01e5641c..22e6893211 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -159,7 +159,6 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY load               SYSTEM "load.sgml">
 <!ENTITY lock               SYSTEM "lock.sgml">
 <!ENTITY move               SYSTEM "move.sgml">
-<!ENTITY merge              SYSTEM "merge.sgml">
 <!ENTITY notify             SYSTEM "notify.sgml">
 <!ENTITY prepare            SYSTEM "prepare.sgml">
 <!ENTITY prepareTransaction SYSTEM "prepare_transaction.sgml">
diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml
index 32f39a48ba..0e35b0ef43 100644
--- a/doc/src/sgml/ref/create_policy.sgml
+++ b/doc/src/sgml/ref/create_policy.sgml
@@ -94,13 +94,6 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
    exist, a <quote>default deny</quote> policy is assumed, so that no rows will
    be visible or updatable.
   </para>
-
-  <para>
-   No separate policy exists for <command>MERGE</command>. Instead policies
-   defined for <literal>SELECT</literal>, <literal>INSERT</literal>,
-   <literal>UPDATE</literal> and <literal>DELETE</literal> are applied
-   while executing MERGE, depending on the actions that are activated.
-  </para>
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index da294aaa46..62e142fd8e 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -579,13 +579,6 @@ INSERT <replaceable>oid</replaceable> <replaceable class="parameter">count</repl
    is a partition, an error will occur if one of the input rows violates
    the partition constraint.
   </para>
-
-  <para>
-   You may also wish to consider using <command>MERGE</command>, since that
-   allows mixed <command>INSERT</command>, <command>UPDATE</command> and
-   <command>DELETE</command> within a single statement.
-   See <xref linkend="sql-merge"/>.
-  </para>
  </refsect1>
 
  <refsect1>
@@ -756,9 +749,7 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
    Also, the case in
    which a column name list is omitted, but not all the columns are
    filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
-   is disallowed by the standard. If you prefer a more SQL Standard
-   conforming statement than <literal>ON CONFLICT</literal>, see
-   <xref linkend="sql-merge"/>.
+   is disallowed by the standard.
   </para>
 
   <para>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ef2270c467..d27fb414f7 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -186,7 +186,6 @@
    &listen;
    &load;
    &lock;
-   &merge;
    &move;
    &notify;
    &prepare;
diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index cce58fbf1d..c43dbc9786 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -182,26 +182,6 @@
     will be fired.
    </para>
 
-   <para>
-    No separate triggers are defined for <command>MERGE</command>. Instead,
-    statement-level or row-level <command>UPDATE</command>,
-    <command>DELETE</command> and <command>INSERT</command> triggers are fired
-    depending on what actions are specified in the <command>MERGE</command> query
-    and what actions are activated.
-   </para>
-
-   <para>
-    While running a <command>MERGE</command> command, statement-level
-    <literal>BEFORE</literal> and <literal>AFTER</literal> triggers are fired for
-    events specified in the actions of the <command>MERGE</command> command,
-    irrespective of whether the action is finally activated or not. This is same as
-    an <command>UPDATE</command> statement that updates no rows, yet
-    statement-level triggers are fired. The row-level triggers are fired only
-    when a row is actually updated, inserted or deleted. So it's perfectly legal
-    that while statement-level triggers are fired for certain type of action, no
-    row-level triggers are fired for the same kind of action.
-   </para>
-
    <para>
     Trigger functions invoked by per-statement triggers should always
     return <symbol>NULL</symbol>. Trigger functions invoked by per-row
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index f96567f5d5..d7279248e7 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3245,7 +3245,6 @@ l1:
 			   result == HeapTupleUpdated ||
 			   result == HeapTupleBeingUpdated);
 		Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->result = result;
 		hufd->ctid = tp.t_data->t_ctid;
 		hufd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
 		if (result == HeapTupleSelfUpdated)
@@ -3508,7 +3507,7 @@ simple_heap_delete(Relation relation, ItemPointer tid)
 HTSU_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd)
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
 {
 	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -3548,10 +3547,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 				infomask2_old_tuple,
 				infomask_new_tuple,
 				infomask2_new_tuple;
-	LockTupleMode	lockmode;
 
 	Assert(ItemPointerIsValid(otid));
-	Assert(hufd != NULL);
 
 	/*
 	 * Forbid this during a parallel operation, lest it allocate a combocid.
@@ -3667,7 +3664,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	 */
 	if (!bms_overlap(modified_attrs, key_attrs))
 	{
-		lockmode = hufd->lockmode = LockTupleNoKeyExclusive;
+		*lockmode = LockTupleNoKeyExclusive;
 		mxact_status = MultiXactStatusNoKeyUpdate;
 		key_intact = true;
 
@@ -3684,7 +3681,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	}
 	else
 	{
-		lockmode = hufd->lockmode = LockTupleExclusive;
+		*lockmode = LockTupleExclusive;
 		mxact_status = MultiXactStatusUpdate;
 		key_intact = false;
 	}
@@ -3762,12 +3759,12 @@ l2:
 			int			remain;
 
 			if (DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
-										lockmode))
+										*lockmode))
 			{
 				LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 				/* acquire tuple lock, if necessary */
-				heap_acquire_tuplock(relation, &(oldtup.t_self), lockmode,
+				heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
 									 LockWaitBlock, &have_tuple_lock);
 
 				/* wait for multixact */
@@ -3851,7 +3848,7 @@ l2:
 			 * lock.
 			 */
 			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-			heap_acquire_tuplock(relation, &(oldtup.t_self), lockmode,
+			heap_acquire_tuplock(relation, &(oldtup.t_self), *lockmode,
 								 LockWaitBlock, &have_tuple_lock);
 			XactLockTableWait(xwait, relation, &oldtup.t_self,
 							  XLTW_Update);
@@ -3890,7 +3887,6 @@ l2:
 			   result == HeapTupleUpdated ||
 			   result == HeapTupleBeingUpdated);
 		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->result = result;
 		hufd->ctid = oldtup.t_data->t_ctid;
 		hufd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
 		if (result == HeapTupleSelfUpdated)
@@ -3899,7 +3895,7 @@ l2:
 			hufd->cmax = InvalidCommandId;
 		UnlockReleaseBuffer(buffer);
 		if (have_tuple_lock)
-			UnlockTupleTuplock(relation, &(oldtup.t_self), lockmode);
+			UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
 		bms_free(hot_attrs);
@@ -3937,7 +3933,7 @@ l2:
 	compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
 							  oldtup.t_data->t_infomask,
 							  oldtup.t_data->t_infomask2,
-							  xid, lockmode, true,
+							  xid, *lockmode, true,
 							  &xmax_old_tuple, &infomask_old_tuple,
 							  &infomask2_old_tuple);
 
@@ -4054,7 +4050,7 @@ l2:
 		compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(oldtup.t_data),
 								  oldtup.t_data->t_infomask,
 								  oldtup.t_data->t_infomask2,
-								  xid, lockmode, false,
+								  xid, *lockmode, false,
 								  &xmax_lock_old_tuple, &infomask_lock_old_tuple,
 								  &infomask2_lock_old_tuple);
 
@@ -4366,7 +4362,7 @@ l2:
 	 * Release the lmgr tuple lock, if we had it.
 	 */
 	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, &(oldtup.t_self), lockmode);
+		UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode);
 
 	pgstat_count_heap_update(relation, use_hot_update);
 
@@ -4590,11 +4586,12 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 {
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
+	LockTupleMode lockmode;
 
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &hufd);
+						 &hufd, &lockmode);
 	switch (result)
 	{
 		case HeapTupleSelfUpdated:
@@ -5180,7 +5177,6 @@ failed:
 		Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
 			   result == HeapTupleWouldBlock);
 		Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
-		hufd->result = result;
 		hufd->ctid = tuple->t_data->t_ctid;
 		hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
 		if (result == HeapTupleSelfUpdated)
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ca0409b83e..20d61f3780 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -229,9 +229,9 @@ F311	Schema definition statement	02	CREATE TABLE for persistent base tables	YES
 F311	Schema definition statement	03	CREATE VIEW	YES	
 F311	Schema definition statement	04	CREATE VIEW: WITH CHECK OPTION	YES	
 F311	Schema definition statement	05	GRANT statement	YES	
-F312	MERGE statement			YES	also consider INSERT ... ON CONFLICT DO UPDATE
-F313	Enhanced MERGE statement			YES	
-F314	MERGE statement with DELETE branch			YES	
+F312	MERGE statement			NO	consider INSERT ... ON CONFLICT DO UPDATE
+F313	Enhanced MERGE statement			NO	
+F314	MERGE statement with DELETE branch			NO	
 F321	User authorization			YES	
 F341	Usage tables			NO	no ROUTINE_*_USAGE tables
 F361	Subprogram support			YES	
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 79f639d5e2..8a58672a94 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -946,9 +946,6 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				case CMD_DELETE:
 					pname = operation = "Delete";
 					break;
-				case CMD_MERGE:
-					pname = operation = "Merge";
-					break;
 				default:
 					pname = "???";
 					break;
@@ -3010,10 +3007,6 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 			operation = "Delete";
 			foperation = "Foreign Delete";
 			break;
-		case CMD_MERGE:
-			operation = "Merge";
-			foperation = "Foreign Merge";
-			break;
 		default:
 			operation = "???";
 			foperation = "Foreign ???";
@@ -3136,32 +3129,6 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 								 other_path, 0, es);
 		}
 	}
-	else if (node->operation == CMD_MERGE)
-	{
-		/* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
-		if (es->analyze && mtstate->ps.instrument)
-		{
-			double		total;
-			double		insert_path;
-			double		update_path;
-			double		delete_path;
-			double		skipped_path;
-
-			InstrEndLoop(mtstate->mt_plans[0]->instrument);
-
-			/* count the number of source rows */
-			total = mtstate->mt_plans[0]->instrument->ntuples;
-			insert_path = mtstate->ps.instrument->nfiltered1;
-			update_path = mtstate->ps.instrument->nfiltered2;
-			delete_path = mtstate->ps.instrument->nfiltered3;
-			skipped_path = total - insert_path - update_path - delete_path;
-
-			ExplainPropertyFloat("Tuples Inserted", NULL, insert_path, 0, es);
-			ExplainPropertyFloat("Tuples Updated", NULL, update_path, 0, es);
-			ExplainPropertyFloat("Tuples Deleted", NULL, delete_path, 0, es);
-			ExplainPropertyFloat("Tuples Skipped", NULL, skipped_path, 0, es);
-		}
-	}
 
 	if (labeltargets)
 		ExplainCloseGroup("Target Tables", "Target Tables", false, es);
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index c3610b1874..b945b1556a 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -151,7 +151,6 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString,
 		case CMD_INSERT:
 		case CMD_UPDATE:
 		case CMD_DELETE:
-		case CMD_MERGE:
 			/* OK */
 			break;
 		default:
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index e71f921fda..a6593f939c 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -85,8 +85,7 @@ static HeapTuple GetTupleForTrigger(EState *estate,
 				   ResultRelInfo *relinfo,
 				   ItemPointer tid,
 				   LockTupleMode lockmode,
-				   TupleTableSlot **newSlot,
-				   HeapUpdateFailureData *hufdp);
+				   TupleTableSlot **newSlot);
 static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 			   Trigger *trigger, TriggerEvent event,
 			   Bitmapset *modifiedCols,
@@ -2730,8 +2729,7 @@ bool
 ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
-					 HeapUpdateFailureData *hufdp)
+					 HeapTuple fdw_trigtuple)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	bool		result = true;
@@ -2745,7 +2743,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 	if (fdw_trigtuple == NULL)
 	{
 		trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
-									   LockTupleExclusive, &newSlot, hufdp);
+									   LockTupleExclusive, &newSlot);
 		if (trigtuple == NULL)
 			return false;
 	}
@@ -2816,7 +2814,6 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 										   relinfo,
 										   tupleid,
 										   LockTupleExclusive,
-										   NULL,
 										   NULL);
 		else
 			trigtuple = fdw_trigtuple;
@@ -2954,8 +2951,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 TupleTableSlot *slot,
-					 HeapUpdateFailureData *hufdp)
+					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	HeapTuple	slottuple = ExecMaterializeSlot(slot);
@@ -2976,7 +2972,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	{
 		/* get a copy of the on-disk tuple we are planning to update */
 		trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
-									   lockmode, &newSlot, hufdp);
+									   lockmode, &newSlot);
 		if (trigtuple == NULL)
 			return NULL;		/* cancel the update action */
 	}
@@ -3096,7 +3092,6 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 										   relinfo,
 										   tupleid,
 										   LockTupleExclusive,
-										   NULL,
 										   NULL);
 		else
 			trigtuple = fdw_trigtuple;
@@ -3245,8 +3240,7 @@ GetTupleForTrigger(EState *estate,
 				   ResultRelInfo *relinfo,
 				   ItemPointer tid,
 				   LockTupleMode lockmode,
-				   TupleTableSlot **newSlot,
-				   HeapUpdateFailureData *hufdp)
+				   TupleTableSlot **newSlot)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
 	HeapTupleData tuple;
@@ -3272,11 +3266,6 @@ ltrmark:;
 							   estate->es_output_cid,
 							   lockmode, LockWaitBlock,
 							   false, &buffer, &hufd);
-
-		/* Let the caller know about failure reason, if any. */
-		if (hufdp)
-			*hufdp = hufd;
-
 		switch (test)
 		{
 			case HeapTupleSelfUpdated:
@@ -3313,17 +3302,10 @@ ltrmark:;
 					/* it was updated, so look at the updated version */
 					TupleTableSlot *epqslot;
 
-					/*
-					 * If we're running MERGE then we must install the
-					 * new tuple in the slot of the underlying join query and
-					 * not the result relation itself. If the join does not
-					 * yield any tuple, the caller will take the necessary
-					 * action.
-					 */
 					epqslot = EvalPlanQual(estate,
 										   epqstate,
 										   relation,
-										   GetEPQRangeTableIndex(relinfo),
+										   relinfo->ri_RangeTableIndex,
 										   lockmode,
 										   &hufd.ctid,
 										   hufd.xmax);
@@ -3846,14 +3828,8 @@ struct AfterTriggersTableData
 	bool		before_trig_done;	/* did we already queue BS triggers? */
 	bool		after_trig_done;	/* did we already queue AS triggers? */
 	AfterTriggerEventList after_trig_events;	/* if so, saved list pointer */
-	/* "old" transition table for UPDATE, if any */
-	Tuplestorestate *old_upd_tuplestore;
-	/* "new" transition table for UPDATE, if any */
-	Tuplestorestate *new_upd_tuplestore;
-	/* "old" transition table for DELETE, if any */
-	Tuplestorestate *old_del_tuplestore;
-	/* "new" transition table INSERT, if any */
-	Tuplestorestate *new_ins_tuplestore;
+	Tuplestorestate *old_tuplestore;	/* "old" transition table, if any */
+	Tuplestorestate *new_tuplestore;	/* "new" transition table, if any */
 };
 
 static AfterTriggersData afterTriggers;
@@ -4320,19 +4296,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	{
 		if (LocTriggerData.tg_trigger->tgoldtable)
 		{
-			if (TRIGGER_FIRED_BY_UPDATE(evtshared->ats_event))
-				LocTriggerData.tg_oldtable = evtshared->ats_table->old_upd_tuplestore;
-			else
-				LocTriggerData.tg_oldtable = evtshared->ats_table->old_del_tuplestore;
+			LocTriggerData.tg_oldtable = evtshared->ats_table->old_tuplestore;
 			evtshared->ats_table->closed = true;
 		}
 
 		if (LocTriggerData.tg_trigger->tgnewtable)
 		{
-			if (TRIGGER_FIRED_BY_INSERT(evtshared->ats_event))
-				LocTriggerData.tg_newtable = evtshared->ats_table->new_ins_tuplestore;
-			else
-				LocTriggerData.tg_newtable = evtshared->ats_table->new_upd_tuplestore;
+			LocTriggerData.tg_newtable = evtshared->ats_table->new_tuplestore;
 			evtshared->ats_table->closed = true;
 		}
 	}
@@ -4667,10 +4637,8 @@ TransitionCaptureState *
 MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
 {
 	TransitionCaptureState *state;
-	bool		need_old_upd,
-				need_new_upd,
-				need_old_del,
-				need_new_ins;
+	bool		need_old,
+				need_new;
 	AfterTriggersTableData *table;
 	MemoryContext oldcxt;
 	ResourceOwner saveResourceOwner;
@@ -4682,31 +4650,23 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
 	switch (cmdType)
 	{
 		case CMD_INSERT:
-			need_old_upd = need_old_del = need_new_upd = false;
-			need_new_ins = trigdesc->trig_insert_new_table;
+			need_old = false;
+			need_new = trigdesc->trig_insert_new_table;
 			break;
 		case CMD_UPDATE:
-			need_old_upd = trigdesc->trig_update_old_table;
-			need_new_upd = trigdesc->trig_update_new_table;
-			need_old_del = need_new_ins = false;
+			need_old = trigdesc->trig_update_old_table;
+			need_new = trigdesc->trig_update_new_table;
 			break;
 		case CMD_DELETE:
-			need_old_del = trigdesc->trig_delete_old_table;
-			need_old_upd = need_new_upd = need_new_ins = false;
-			break;
-		case CMD_MERGE:
-			need_old_upd = trigdesc->trig_update_old_table;
-			need_new_upd = trigdesc->trig_update_new_table;
-			need_old_del = trigdesc->trig_delete_old_table;
-			need_new_ins = trigdesc->trig_insert_new_table;
+			need_old = trigdesc->trig_delete_old_table;
+			need_new = false;
 			break;
 		default:
 			elog(ERROR, "unexpected CmdType: %d", (int) cmdType);
-			/* keep compiler quiet */
-			need_old_upd = need_new_upd = need_old_del = need_new_ins = false;
+			need_old = need_new = false;	/* keep compiler quiet */
 			break;
 	}
-	if (!need_old_upd && !need_new_upd && !need_new_ins && !need_old_del)
+	if (!need_old && !need_new)
 		return NULL;
 
 	/* Check state, like AfterTriggerSaveEvent. */
@@ -4736,14 +4696,10 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
 	saveResourceOwner = CurrentResourceOwner;
 	CurrentResourceOwner = CurTransactionResourceOwner;
 
-	if (need_old_upd && table->old_upd_tuplestore == NULL)
-		table->old_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
-	if (need_new_upd && table->new_upd_tuplestore == NULL)
-		table->new_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
-	if (need_old_del && table->old_del_tuplestore == NULL)
-		table->old_del_tuplestore = tuplestore_begin_heap(false, false, work_mem);
-	if (need_new_ins && table->new_ins_tuplestore == NULL)
-		table->new_ins_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+	if (need_old && table->old_tuplestore == NULL)
+		table->old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+	if (need_new && table->new_tuplestore == NULL)
+		table->new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
 
 	CurrentResourceOwner = saveResourceOwner;
 	MemoryContextSwitchTo(oldcxt);
@@ -4932,20 +4888,12 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
 	{
 		AfterTriggersTableData *table = (AfterTriggersTableData *) lfirst(lc);
 
-		ts = table->old_upd_tuplestore;
-		table->old_upd_tuplestore = NULL;
+		ts = table->old_tuplestore;
+		table->old_tuplestore = NULL;
 		if (ts)
 			tuplestore_end(ts);
-		ts = table->new_upd_tuplestore;
-		table->new_upd_tuplestore = NULL;
-		if (ts)
-			tuplestore_end(ts);
-		ts = table->old_del_tuplestore;
-		table->old_del_tuplestore = NULL;
-		if (ts)
-			tuplestore_end(ts);
-		ts = table->new_ins_tuplestore;
-		table->new_ins_tuplestore = NULL;
+		ts = table->new_tuplestore;
+		table->new_tuplestore = NULL;
 		if (ts)
 			tuplestore_end(ts);
 	}
@@ -5796,28 +5744,12 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 				 newtup == NULL));
 
 		if (oldtup != NULL &&
-			(event == TRIGGER_EVENT_DELETE && delete_old_table))
+			((event == TRIGGER_EVENT_DELETE && delete_old_table) ||
+			 (event == TRIGGER_EVENT_UPDATE && update_old_table)))
 		{
 			Tuplestorestate *old_tuplestore;
 
-			old_tuplestore = transition_capture->tcs_private->old_del_tuplestore;
-
-			if (map != NULL)
-			{
-				HeapTuple	converted = do_convert_tuple(oldtup, map);
-
-				tuplestore_puttuple(old_tuplestore, converted);
-				pfree(converted);
-			}
-			else
-				tuplestore_puttuple(old_tuplestore, oldtup);
-		}
-		if (oldtup != NULL &&
-			 (event == TRIGGER_EVENT_UPDATE && update_old_table))
-		{
-			Tuplestorestate *old_tuplestore;
-
-			old_tuplestore = transition_capture->tcs_private->old_upd_tuplestore;
+			old_tuplestore = transition_capture->tcs_private->old_tuplestore;
 
 			if (map != NULL)
 			{
@@ -5830,30 +5762,12 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 				tuplestore_puttuple(old_tuplestore, oldtup);
 		}
 		if (newtup != NULL &&
-			(event == TRIGGER_EVENT_INSERT && insert_new_table))
+			((event == TRIGGER_EVENT_INSERT && insert_new_table) ||
+			(event == TRIGGER_EVENT_UPDATE && update_new_table)))
 		{
 			Tuplestorestate *new_tuplestore;
 
-			new_tuplestore = transition_capture->tcs_private->new_ins_tuplestore;
-
-			if (original_insert_tuple != NULL)
-				tuplestore_puttuple(new_tuplestore, original_insert_tuple);
-			else if (map != NULL)
-			{
-				HeapTuple	converted = do_convert_tuple(newtup, map);
-
-				tuplestore_puttuple(new_tuplestore, converted);
-				pfree(converted);
-			}
-			else
-				tuplestore_puttuple(new_tuplestore, newtup);
-		}
-		if (newtup != NULL &&
-			(event == TRIGGER_EVENT_UPDATE && update_new_table))
-		{
-			Tuplestorestate *new_tuplestore;
-
-			new_tuplestore = transition_capture->tcs_private->new_upd_tuplestore;
+			new_tuplestore = transition_capture->tcs_private->new_tuplestore;
 
 			if (original_insert_tuple != NULL)
 				tuplestore_puttuple(new_tuplestore, original_insert_tuple);
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 68675f9796..cc09895fa5 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -22,7 +22,7 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
        nodeCustom.o nodeFunctionscan.o nodeGather.o \
        nodeHash.o nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
        nodeLimit.o nodeLockRows.o nodeGatherMerge.o \
-       nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeMerge.o nodeModifyTable.o \
+       nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
        nodeNestloop.o nodeProjectSet.o nodeRecursiveunion.o nodeResult.o \
        nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
        nodeValuesscan.o \
diff --git a/src/backend/executor/README b/src/backend/executor/README
index 05769772b7..0d7cd552eb 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -37,16 +37,6 @@ the plan tree returns the computed tuples to be updated, plus a "junk"
 one.  For DELETE, the plan tree need only deliver a CTID column, and the
 ModifyTable node visits each of those rows and marks the row deleted.
 
-MERGE runs one generic plan that returns candidate target rows. Each row
-consists of a super-row that contains all the columns needed by any of the
-individual actions, plus a CTID and a TABLEOID junk columns. The CTID column is
-required to know if a matching target row was found or not and the TABLEOID
-column is needed to find the underlying target partition, in case when the
-target table is a partition table. If the CTID column is set we attempt to
-activate WHEN MATCHED actions, or if it is NULL then we will attempt to
-activate WHEN NOT MATCHED actions. Once we know which action is activated we
-form the final result row and apply only those changes.
-
 XXX a great deal more documentation needs to be written here...
 
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index e4d9b0b3f8..9a107aba56 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -233,7 +233,6 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		case CMD_INSERT:
 		case CMD_DELETE:
 		case CMD_UPDATE:
-		case CMD_MERGE:
 			estate->es_output_cid = GetCurrentCommandId(true);
 			break;
 
@@ -1358,9 +1357,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_onConflictArbiterIndexes = NIL;
 	resultRelInfo->ri_onConflict = NULL;
 
-	resultRelInfo->ri_mergeTargetRTI = 0;
-	resultRelInfo->ri_mergeState = (MergeState *) palloc0(sizeof (MergeState));
-
 	/*
 	 * Partition constraint, which also includes the partition constraint of
 	 * all the ancestors that are partitions.  Note that it will be checked
@@ -2209,19 +2205,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 								 errmsg("new row violates row-level security policy for table \"%s\"",
 										wco->relname)));
 					break;
-				case WCO_RLS_MERGE_UPDATE_CHECK:
-				case WCO_RLS_MERGE_DELETE_CHECK:
-					if (wco->polname != NULL)
-						ereport(ERROR,
-								(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-								 errmsg("target row violates row-level security policy \"%s\" (USING expression) for table \"%s\"",
-										wco->polname, wco->relname)));
-					else
-						ereport(ERROR,
-								(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-								 errmsg("target row violates row-level security policy (USING expression) for table \"%s\"",
-										wco->relname)));
-					break;
 				case WCO_RLS_CONFLICT_CHECK:
 					if (wco->polname != NULL)
 						ereport(ERROR,
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index a6a7885abd..9a13188649 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -67,8 +67,6 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 	ResultRelInfo *update_rri = NULL;
 	int			num_update_rri = 0,
 				update_rri_index = 0;
-	bool		is_update = false;
-	bool		is_merge = false;
 	PartitionTupleRouting *proute;
 	int			nparts;
 	ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
@@ -91,22 +89,13 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 
 	/* Set up details specific to the type of tuple routing we are doing. */
 	if (node && node->operation == CMD_UPDATE)
-		is_update = true;
-	else if (node && node->operation == CMD_MERGE)
-		is_merge = true;
-
-	if (is_update)
 	{
 		update_rri = mtstate->resultRelInfo;
 		num_update_rri = list_length(node->plans);
 		proute->subplan_partition_offsets =
 			palloc(num_update_rri * sizeof(int));
 		proute->num_subplan_partition_offsets = num_update_rri;
-	}
 
-
-	if (is_update || is_merge)
-	{
 		/*
 		 * We need an additional tuple slot for storing transient tuples that
 		 * are converted to the root table descriptor.
@@ -310,25 +299,6 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 	return result;
 }
 
-/*
- * Given OID of the partition leaf, return the index of the leaf in the
- * partition hierarchy.
- */
-int
-ExecFindPartitionByOid(PartitionTupleRouting *proute, Oid partoid)
-{
-	int	i;
-
-	for (i = 0; i < proute->num_partitions; i++)
-	{
-		if (proute->partition_oids[i] == partoid)
-			break;
-	}
-
-	Assert(i < proute->num_partitions);
-	return i;
-}
-
 /*
  * ExecInitPartitionInfo
  *		Initialize ResultRelInfo and other information for a partition if not
@@ -367,8 +337,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
 					  rootrel,
 					  estate->es_instrument);
 
-	leaf_part_rri->ri_PartitionLeafIndex = partidx;
-
 	/*
 	 * Verify result relation is a valid target for an INSERT.  An UPDATE of a
 	 * partition-key becomes a DELETE+INSERT operation, so this check is still
@@ -657,90 +625,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
 	Assert(proute->partitions[partidx] == NULL);
 	proute->partitions[partidx] = leaf_part_rri;
 
-	/*
-	 * Initialize information about this partition that's needed to handle
-	 * MERGE.
-	 */
-	if (node && node->operation == CMD_MERGE)
-	{
-		TupleDesc	partrelDesc = RelationGetDescr(partrel);
-		TupleConversionMap *map = proute->parent_child_tupconv_maps[partidx];
-		int			firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
-		Relation	firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
-
-		/*
-		 * If the root parent and partition have the same tuple
-		 * descriptor, just reuse the original MERGE state for partition.
-		 */
-		if (map == NULL)
-		{
-			leaf_part_rri->ri_mergeState = resultRelInfo->ri_mergeState;
-		}
-		else
-		{
-			/* Convert expressions contain partition's attnos. */
-			List	   *conv_tl, *conv_qual;
-			ListCell   *l;
-			List	   *matchedActionStates = NIL;
-			List	   *notMatchedActionStates = NIL;
-
-			foreach (l, node->mergeActionList)
-			{
-				MergeAction *action = lfirst_node(MergeAction, l);
-				MergeActionState *action_state = makeNode(MergeActionState);
-				TupleDesc	tupDesc;
-				ExprContext *econtext;
-
-				action_state->matched = action->matched;
-				action_state->commandType = action->commandType;
-
-				conv_qual = (List *) action->qual;
-				conv_qual = map_partition_varattnos(conv_qual,
-							firstVarno, partrel,
-							firstResultRel, NULL);
-
-				action_state->whenqual = ExecInitQual(conv_qual, &mtstate->ps);
-
-				conv_tl = (List *) action->targetList;
-				conv_tl = map_partition_varattnos(conv_tl,
-							firstVarno, partrel,
-							firstResultRel, NULL);
-
-				conv_tl = adjust_partition_tlist( conv_tl, map);
-
-				tupDesc = ExecTypeFromTL(conv_tl, partrelDesc->tdhasoid);
-				action_state->tupDesc = tupDesc;
-
-				/* build action projection state */
-				econtext = mtstate->ps.ps_ExprContext;
-				action_state->proj =
-					ExecBuildProjectionInfo(conv_tl, econtext,
-							mtstate->mt_mergeproj,
-							&mtstate->ps,
-							partrelDesc);
-
-				if (action_state->matched)
-					matchedActionStates =
-						lappend(matchedActionStates, action_state);
-				else
-					notMatchedActionStates =
-						lappend(notMatchedActionStates, action_state);
-			}
-			leaf_part_rri->ri_mergeState->matchedActionStates =
-				matchedActionStates;
-			leaf_part_rri->ri_mergeState->notMatchedActionStates =
-				notMatchedActionStates;
-		}
-
-		/*
-		 * get_partition_dispatch_recurse() and expand_partitioned_rtentry()
-		 * fetch the leaf OIDs in the same order. So we can safely derive the
-		 * index of the merge target relation corresponding to this partition
-		 * by simply adding partidx + 1 to the root's merge target relation.
-		 */
-		leaf_part_rri->ri_mergeTargetRTI = node->mergeTargetRelation +
-			partidx + 1;
-	}
 	MemoryContextSwitchTo(oldContext);
 
 	return leaf_part_rri;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 971f92a938..32891abbdf 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -454,7 +454,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
 									&searchslot->tts_tuple->t_self,
-									NULL, slot, NULL);
+									NULL, slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			skip_tuple = true;
@@ -515,7 +515,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 	{
 		skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
 										   &searchslot->tts_tuple->t_self,
-										   NULL, NULL);
+										   NULL);
 	}
 
 	if (!skip_tuple)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b03db64e8e..1b09868ff8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,7 +42,6 @@
 #include "commands/trigger.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
-#include "executor/nodeMerge.h"
 #include "executor/nodeModifyTable.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -63,17 +62,17 @@ static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
 					 EState *estate,
 					 bool canSetTag,
 					 TupleTableSlot **returning);
+static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
+						EState *estate,
+						PartitionTupleRouting *proute,
+						ResultRelInfo *targetRelInfo,
+						TupleTableSlot *slot);
 static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
 static void ExecSetupChildParentMapForTcs(ModifyTableState *mtstate);
 static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
 static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
 						int whichplan);
 
-/* flags for mt_merge_subcommands */
-#define MERGE_INSERT	0x01
-#define MERGE_UPDATE	0x02
-#define MERGE_DELETE	0x04
-
 /*
  * Verify that the tuples to be produced by INSERT or UPDATE match the
  * target relation's rowtype
@@ -260,12 +259,11 @@ ExecCheckTIDVisible(EState *estate,
  *		Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
  */
-extern TupleTableSlot *
+static TupleTableSlot *
 ExecInsert(ModifyTableState *mtstate,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   EState *estate,
-		   MergeActionState *actionState,
 		   bool canSetTag)
 {
 	HeapTuple	tuple;
@@ -392,17 +390,9 @@ ExecInsert(ModifyTableState *mtstate,
 		 * partition, we should instead check UPDATE policies, because we are
 		 * executing policies defined on the target table, and not those
 		 * defined on the child partitions.
-		 *
-		 * If we're running MERGE, we refer to the action that we're executing
-		 * to know if we're doing an INSERT or UPDATE to a partition table.
 		 */
-		if (mtstate->operation == CMD_UPDATE)
-			wco_kind = WCO_RLS_UPDATE_CHECK;
-		else if (mtstate->operation == CMD_MERGE)
-			wco_kind = (actionState->commandType == CMD_UPDATE) ?
-				WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
-		else
-			wco_kind = WCO_RLS_INSERT_CHECK;
+		wco_kind = (mtstate->operation == CMD_UPDATE) ?
+			WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
 
 		/*
 		 * ExecWithCheckOptions() will skip any WCOs which are not of the kind
@@ -627,19 +617,10 @@ ExecInsert(ModifyTableState *mtstate,
  *		passed to foreign table triggers; it is NULL when the foreign
  *		table has no relevant triggers.
  *
- *		MERGE passes actionState of the action it's currently executing;
- *		regular DELETE passes NULL. This is used by ExecDelete to know if it's
- *		being called from MERGE or regular DELETE operation.
- *
- *		If the DELETE fails because the tuple is concurrently updated/deleted
- *		by this or some other transaction, hufdp is filled with the reason as
- *		well as other important information. Currently only MERGE needs this
- *		information.
- *
  *		Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
  */
-TupleTableSlot *
+static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
 		   HeapTuple oldtuple,
@@ -648,8 +629,6 @@ ExecDelete(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool *tupleDeleted,
 		   bool processReturning,
-		   HeapUpdateFailureData *hufdp,
-		   MergeActionState *actionState,
 		   bool canSetTag)
 {
 	ResultRelInfo *resultRelInfo;
@@ -662,14 +641,6 @@ ExecDelete(ModifyTableState *mtstate,
 	if (tupleDeleted)
 		*tupleDeleted = false;
 
-	/*
-	 * Initialize hufdp. Since the caller is only interested in the failure
-	 * status, initialize with the state that is used to indicate successful
-	 * operation.
-	 */
-	if (hufdp)
-		hufdp->result = HeapTupleMayBeUpdated;
-
 	/*
 	 * get information on the (current) result relation
 	 */
@@ -683,7 +654,7 @@ ExecDelete(ModifyTableState *mtstate,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid, oldtuple, hufdp);
+										tupleid, oldtuple);
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -750,15 +721,6 @@ ldelete:;
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
 							 &hufd);
-
-		/*
-		 * Copy the necessary information, if the caller has asked for it. We
-		 * must do this irrespective of whether the tuple was updated or
-		 * deleted.
-		 */
-		if (hufdp)
-			*hufdp = hufd;
-
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -793,11 +755,7 @@ ldelete:;
 							 errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
 							 errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
 
-				/*
-				 * Else, already deleted by self; nothing to do but inform
-				 * MERGE about it anyways so that it can take necessary
-				 * action.
-				 */
+				/* Else, already deleted by self; nothing to do */
 				return NULL;
 
 			case HeapTupleMayBeUpdated:
@@ -808,24 +766,14 @@ ldelete:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-
 				if (!ItemPointerEquals(tupleid, &hufd.ctid))
 				{
 					TupleTableSlot *epqslot;
 
-					/*
-					 * If we're executing MERGE, then the onus of running
-					 * EvalPlanQual() and handling its outcome lies with the
-					 * caller.
-					 */
-					if (actionState != NULL)
-						return NULL;
-
-					/* Normal DELETE path.  */
 					epqslot = EvalPlanQual(estate,
 										   epqstate,
 										   resultRelationDesc,
-										   GetEPQRangeTableIndex(resultRelInfo),
+										   resultRelInfo->ri_RangeTableIndex,
 										   LockTupleExclusive,
 										   &hufd.ctid,
 										   hufd.xmax);
@@ -835,12 +783,7 @@ ldelete:;
 						goto ldelete;
 					}
 				}
-
-				/*
-				 * tuple already deleted; nothing to do. But MERGE might want
-				 * to handle it differently. We've already filled-in hufdp
-				 * with sufficient information for MERGE to look at.
-				 */
+				/* tuple already deleted; nothing to do */
 				return NULL;
 
 			default:
@@ -968,21 +911,10 @@ ldelete:;
  *		foreign table triggers; it is NULL when the foreign table has
  *		no relevant triggers.
  *
- *		MERGE passes actionState of the action it's currently executing;
- *		regular UPDATE passes NULL. This is used by ExecUpdate to know if it's
- *		being called from MERGE or regular UPDATE operation. ExecUpdate may
- *		pass this information to ExecInsert if it ends up running DELETE+INSERT
- *		for partition key updates.
- *
- *		If the UPDATE fails because the tuple is concurrently updated/deleted
- *		by this or some other transaction, hufdp is filled with the reason as
- *		well as other important information. Currently only MERGE needs this
- *		information.
- *
  *		Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
  */
-extern TupleTableSlot *
+static TupleTableSlot *
 ExecUpdate(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
 		   HeapTuple oldtuple,
@@ -990,9 +922,6 @@ ExecUpdate(ModifyTableState *mtstate,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
-		   bool *tuple_updated,
-		   HeapUpdateFailureData *hufdp,
-		   MergeActionState *actionState,
 		   bool canSetTag)
 {
 	HeapTuple	tuple;
@@ -1009,17 +938,6 @@ ExecUpdate(ModifyTableState *mtstate,
 	if (IsBootstrapProcessingMode())
 		elog(ERROR, "cannot UPDATE during bootstrap");
 
-	if (tuple_updated)
-		*tuple_updated = false;
-
-	/*
-	 * Initialize hufdp. Since the caller is only interested in the failure
-	 * status, initialize with the state that is used to indicate successful
-	 * operation.
-	 */
-	if (hufdp)
-		hufdp->result = HeapTupleMayBeUpdated;
-
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
@@ -1037,7 +955,7 @@ ExecUpdate(ModifyTableState *mtstate,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, oldtuple, slot, hufdp);
+									tupleid, oldtuple, slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -1083,6 +1001,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	}
 	else
 	{
+		LockTupleMode lockmode;
 		bool		partition_constraint_failed;
 
 		/*
@@ -1160,9 +1079,8 @@ lreplace:;
 			 * Row movement, part 1.  Delete the tuple, but skip RETURNING
 			 * processing. We want to return rows from INSERT.
 			 */
-			ExecDelete(mtstate, tupleid, oldtuple, planSlot, epqstate,
-					   estate, &tuple_deleted, false, hufdp, NULL,
-					   false);
+			ExecDelete(mtstate, tupleid, oldtuple, planSlot, epqstate, estate,
+					   &tuple_deleted, false, false);
 
 			/*
 			 * For some reason if DELETE didn't happen (e.g. trigger prevented
@@ -1198,36 +1116,16 @@ lreplace:;
 				saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
 
 			/*
-			 * We should convert the tuple into root's tuple descriptor, since
-			 * ExecInsert() starts the search from root. To do that, we need to
-			 * retrieve the tuple conversion map for this resultRelInfo.
-			 *
-			 * If we're running MERGE then resultRelInfo is per-partition
-			 * resultRelInfo as initialized in ExecInitPartitionInfo(). Note
-			 * that we don't expand inheritance for the resultRelation in case
-			 * of MERGE and hence there is just one subplan. Whereas for
-			 * regular UPDATE, resultRelInfo is one of the per-subplan
-			 * resultRelInfos. In either case the position of this partition in
-			 * tracked in ri_PartitionLeafIndex;
-			 *
-			 * Retrieve the map either by looking at the resultRelInfo's
-			 * position in mtstate->resultRelInfo[] (for UPDATE) or by simply
-			 * using the ri_PartitionLeafIndex value (for MERGE).
+			 * resultRelInfo is one of the per-subplan resultRelInfos.  So we
+			 * should convert the tuple into root's tuple descriptor, since
+			 * ExecInsert() starts the search from root.  The tuple conversion
+			 * map list is in the order of mtstate->resultRelInfo[], so to
+			 * retrieve the one for this resultRel, we need to know the
+			 * position of the resultRel in mtstate->resultRelInfo[].
 			 */
-			if (mtstate->operation == CMD_MERGE)
-			{
-				map_index = resultRelInfo->ri_PartitionLeafIndex;
-				Assert(mtstate->rootResultRelInfo == NULL);
-				tupconv_map = TupConvMapForLeaf(proute,
-								mtstate->resultRelInfo,
-								map_index);
-			}
-			else
-			{
-				map_index = resultRelInfo - mtstate->resultRelInfo;
-				Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
-				tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
-			}
+			map_index = resultRelInfo - mtstate->resultRelInfo;
+			Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
+			tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
 			tuple = ConvertPartitionTupleSlot(tupconv_map,
 											  tuple,
 											  proute->root_tuple_slot,
@@ -1237,16 +1135,12 @@ lreplace:;
 			 * Prepare for tuple routing, making it look like we're inserting
 			 * into the root.
 			 */
+			Assert(mtstate->rootResultRelInfo != NULL);
 			slot = ExecPrepareTupleRouting(mtstate, estate, proute,
-										   getTargetResultRelInfo(mtstate),
-										   slot);
+										   mtstate->rootResultRelInfo, slot);
 
 			ret_slot = ExecInsert(mtstate, slot, planSlot,
-								  estate, actionState, canSetTag);
-
-			/* Update is successful. */
-			if (tuple_updated)
-				*tuple_updated = true;
+								  estate, canSetTag);
 
 			/* Revert ExecPrepareTupleRouting's node change. */
 			estate->es_result_relation_info = resultRelInfo;
@@ -1284,16 +1178,7 @@ lreplace:;
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
-							 &hufd);
-
-		/*
-		 * Copy the necessary information, if the caller has asked for it. We
-		 * must do this irrespective of whether the tuple was updated or
-		 * deleted.
-		 */
-		if (hufdp)
-			*hufdp = hufd;
-
+							 &hufd, &lockmode);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1338,42 +1223,26 @@ lreplace:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-
 				if (!ItemPointerEquals(tupleid, &hufd.ctid))
 				{
 					TupleTableSlot *epqslot;
 
-					/*
-					 * If we're executing MERGE, then the onus of running
-					 * EvalPlanQual() and handling its outcome lies with the
-					 * caller.
-					 */
-					if (actionState != NULL)
-						return NULL;
-
-					/* Regular UPDATE path. */
 					epqslot = EvalPlanQual(estate,
 										   epqstate,
 										   resultRelationDesc,
-										   GetEPQRangeTableIndex(resultRelInfo),
-										   hufd.lockmode,
+										   resultRelInfo->ri_RangeTableIndex,
+										   lockmode,
 										   &hufd.ctid,
 										   hufd.xmax);
 					if (!TupIsNull(epqslot))
 					{
 						*tupleid = hufd.ctid;
-						/* Normal UPDATE path */
 						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
 						tuple = ExecMaterializeSlot(slot);
 						goto lreplace;
 					}
 				}
-
-				/*
-				 * tuple already deleted; nothing to do. But MERGE might want
-				 * to handle it differently. We've already filled-in hufdp
-				 * with sufficient information for MERGE to look at.
-				 */
+				/* tuple already deleted; nothing to do */
 				return NULL;
 
 			default:
@@ -1402,9 +1271,6 @@ lreplace:;
 												   estate, false, NULL, NIL);
 	}
 
-	if (tuple_updated)
-		*tuple_updated = true;
-
 	if (canSetTag)
 		(estate->es_processed)++;
 
@@ -1499,9 +1365,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 			 * there's no historical behavior to break.
 			 *
 			 * It is the user's responsibility to prevent this situation from
-			 * occurring.  These problems are why SQL Standard similarly
-			 * specifies that for SQL MERGE, an exception must be raised in
-			 * the event of an attempt to update the same row twice.
+			 * occurring.  These problems are why SQL-2003 similarly specifies
+			 * that for SQL MERGE, an exception must be raised in the event of
+			 * an attempt to update the same row twice.
 			 */
 			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data)))
 				ereport(ERROR,
@@ -1623,7 +1489,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
 							mtstate->mt_conflproj, planSlot,
 							&mtstate->mt_epqstate, mtstate->ps.state,
-							NULL, NULL, NULL, canSetTag);
+							canSetTag);
 
 	ReleaseBuffer(buffer);
 	return true;
@@ -1661,14 +1527,6 @@ fireBSTriggers(ModifyTableState *node)
 		case CMD_DELETE:
 			ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
-		case CMD_MERGE:
-			if (node->mt_merge_subcommands & MERGE_INSERT)
-				ExecBSInsertTriggers(node->ps.state, resultRelInfo);
-			if (node->mt_merge_subcommands & MERGE_UPDATE)
-				ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
-			if (node->mt_merge_subcommands & MERGE_DELETE)
-				ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
-			break;
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -1724,17 +1582,6 @@ fireASTriggers(ModifyTableState *node)
 			ExecASDeleteTriggers(node->ps.state, resultRelInfo,
 								 node->mt_transition_capture);
 			break;
-		case CMD_MERGE:
-			if (node->mt_merge_subcommands & MERGE_DELETE)
-				ExecASDeleteTriggers(node->ps.state, resultRelInfo,
-									 node->mt_transition_capture);
-			if (node->mt_merge_subcommands & MERGE_UPDATE)
-				ExecASUpdateTriggers(node->ps.state, resultRelInfo,
-									 node->mt_transition_capture);
-			if (node->mt_merge_subcommands & MERGE_INSERT)
-				ExecASInsertTriggers(node->ps.state, resultRelInfo,
-									 node->mt_transition_capture);
-			break;
 		default:
 			elog(ERROR, "unknown operation");
 			break;
@@ -1797,7 +1644,7 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
  *
  * Returns a slot holding the tuple of the partition rowtype.
  */
-TupleTableSlot *
+static TupleTableSlot *
 ExecPrepareTupleRouting(ModifyTableState *mtstate,
 						EState *estate,
 						PartitionTupleRouting *proute,
@@ -2120,7 +1967,6 @@ ExecModifyTable(PlanState *pstate)
 		{
 			/* advance to next subplan if any */
 			node->mt_whichplan++;
-
 			if (node->mt_whichplan < node->mt_nplans)
 			{
 				resultRelInfo++;
@@ -2169,12 +2015,6 @@ ExecModifyTable(PlanState *pstate)
 		EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
 		slot = planSlot;
 
-		if (operation == CMD_MERGE)
-		{
-			ExecMerge(node, estate, slot, junkfilter, resultRelInfo);
-			continue;
-		}
-
 		tupleid = NULL;
 		oldtuple = NULL;
 		if (junkfilter != NULL)
@@ -2256,20 +2096,19 @@ ExecModifyTable(PlanState *pstate)
 					slot = ExecPrepareTupleRouting(node, estate, proute,
 												   resultRelInfo, slot);
 				slot = ExecInsert(node, slot, planSlot,
-								  estate, NULL, node->canSetTag);
+								  estate, node->canSetTag);
 				/* Revert ExecPrepareTupleRouting's state change. */
 				if (proute)
 					estate->es_result_relation_info = resultRelInfo;
 				break;
 			case CMD_UPDATE:
 				slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
-								  &node->mt_epqstate, estate,
-								  NULL, NULL, NULL, node->canSetTag);
+								  &node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
 				slot = ExecDelete(node, tupleid, oldtuple, planSlot,
 								  &node->mt_epqstate, estate,
-								  NULL, true, NULL, NULL, node->canSetTag);
+								  NULL, true, node->canSetTag);
 				break;
 			default:
 				elog(ERROR, "unknown operation");
@@ -2359,16 +2198,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	saved_resultRelInfo = estate->es_result_relation_info;
 
 	resultRelInfo = mtstate->resultRelInfo;
-
-	/*
-	 * mergeTargetRelation must be set if we're running MERGE and mustn't be
-	 * set if we're not.
-	 */
-	Assert(operation != CMD_MERGE || node->mergeTargetRelation > 0);
-	Assert(operation == CMD_MERGE || node->mergeTargetRelation == 0);
-
-	resultRelInfo->ri_mergeTargetRTI = node->mergeTargetRelation;
-
 	i = 0;
 	foreach(l, node->plans)
 	{
@@ -2447,8 +2276,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 * partition key.
 	 */
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-		(operation == CMD_INSERT || operation == CMD_MERGE ||
-		 update_tuple_routing_needed))
+		(operation == CMD_INSERT || update_tuple_routing_needed))
 		mtstate->mt_partition_tuple_routing =
 						ExecSetupPartitionTupleRouting(mtstate, rel);
 
@@ -2459,15 +2287,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
 		ExecSetupTransitionCaptureState(mtstate, estate);
 
-	/*
-	 * If we are doing MERGE then setup child-parent mapping. This will be
-	 * required in case we end up doing a partition-key update, triggering a
-	 * tuple routing.
-	 */
-	if (mtstate->operation == CMD_MERGE &&
-		mtstate->mt_partition_tuple_routing != NULL)
-		ExecSetupChildParentMapForLeaf(mtstate->mt_partition_tuple_routing);
-
 	/*
 	 * Construct mapping from each of the per-subplan partition attnos to the
 	 * root attno.  This is required when during update row movement the tuple
@@ -2659,106 +2478,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		}
 	}
 
-	resultRelInfo = mtstate->resultRelInfo;
-
-	if (node->mergeActionList)
-	{
-		ListCell   *l;
-		ExprContext *econtext;
-		List	   *mergeMatchedActionStates = NIL;
-		List	   *mergeNotMatchedActionStates = NIL;
-		TupleDesc	relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
-		mtstate->mt_merge_subcommands = 0;
-
-		if (mtstate->ps.ps_ExprContext == NULL)
-			ExecAssignExprContext(estate, &mtstate->ps);
-
-		econtext = mtstate->ps.ps_ExprContext;
-
-		/* initialize slot for the existing tuple */
-		Assert(mtstate->mt_existing == NULL);
-		mtstate->mt_existing =
-			ExecInitExtraTupleSlot(mtstate->ps.state,
-								   mtstate->mt_partition_tuple_routing ?
-								   NULL : relationDesc);
-
-		/* initialize slot for merge actions */
-		Assert(mtstate->mt_mergeproj == NULL);
-		mtstate->mt_mergeproj =
-			ExecInitExtraTupleSlot(mtstate->ps.state,
-								   mtstate->mt_partition_tuple_routing ?
-								   NULL : relationDesc);
-
-		/*
-		 * Create a MergeActionState for each action on the mergeActionList
-		 * and add it to either a list of matched actions or not-matched
-		 * actions.
-		 */
-		foreach(l, node->mergeActionList)
-		{
-			MergeAction *action = (MergeAction *) lfirst(l);
-			MergeActionState *action_state = makeNode(MergeActionState);
-			TupleDesc	tupDesc;
-
-			action_state->matched = action->matched;
-			action_state->commandType = action->commandType;
-			action_state->whenqual = ExecInitQual((List *) action->qual,
-					&mtstate->ps);
-
-			/* create target slot for this action's projection */
-			tupDesc = ExecTypeFromTL((List *) action->targetList,
-					resultRelInfo->ri_RelationDesc->rd_rel->relhasoids);
-			action_state->tupDesc = tupDesc;
-
-			/* build action projection state */
-			action_state->proj =
-				ExecBuildProjectionInfo(action->targetList, econtext,
-						mtstate->mt_mergeproj, &mtstate->ps,
-						resultRelInfo->ri_RelationDesc->rd_att);
-
-			/*
-			 * We create two lists - one for WHEN MATCHED actions and one
-			 * for WHEN NOT MATCHED actions - and stick the
-			 * MergeActionState into the appropriate list.
-			 */
-			if (action_state->matched)
-				mergeMatchedActionStates =
-					lappend(mergeMatchedActionStates, action_state);
-			else
-				mergeNotMatchedActionStates =
-					lappend(mergeNotMatchedActionStates, action_state);
-
-			switch (action->commandType)
-			{
-				case CMD_INSERT:
-					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
-										action->targetList);
-					mtstate->mt_merge_subcommands |= MERGE_INSERT;
-					break;
-				case CMD_UPDATE:
-					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
-										action->targetList);
-					mtstate->mt_merge_subcommands |= MERGE_UPDATE;
-					break;
-				case CMD_DELETE:
-					mtstate->mt_merge_subcommands |= MERGE_DELETE;
-					break;
-				case CMD_NOTHING:
-					break;
-				default:
-					elog(ERROR, "unknown operation");
-					break;
-			}
-
-			resultRelInfo->ri_mergeState->matchedActionStates =
-						mergeMatchedActionStates;
-			resultRelInfo->ri_mergeState->notMatchedActionStates =
-						mergeNotMatchedActionStates;
-
-		}
-	}
-
 	/* select first subplan */
 	mtstate->mt_whichplan = 0;
 	subplan = (Plan *) linitial(node->plans);
@@ -2772,7 +2491,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 * --- no need to look first.  Typically, this will be a 'ctid' or
 	 * 'wholerow' attribute, but in the case of a foreign data wrapper it
 	 * might be a set of junk attributes sufficient to identify the remote
-	 * row. We follow this logic for MERGE, so it always has a junk attributes.
+	 * row.
 	 *
 	 * If there are multiple result relations, each one needs its own junk
 	 * filter.  Note multiple rels are only possible for UPDATE/DELETE, so we
@@ -2800,7 +2519,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				break;
 			case CMD_UPDATE:
 			case CMD_DELETE:
-			case CMD_MERGE:
 				junk_filter_needed = true;
 				break;
 			default:
@@ -2816,7 +2534,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				JunkFilter *j;
 
 				subplan = mtstate->mt_plans[i]->plan;
-
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
 					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
 										subplan->targetlist);
@@ -2825,9 +2542,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 									   resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
 									   ExecInitExtraTupleSlot(estate, NULL));
 
-				if (operation == CMD_UPDATE ||
-					operation == CMD_DELETE ||
-					operation == CMD_MERGE)
+				if (operation == CMD_UPDATE || operation == CMD_DELETE)
 				{
 					/* For UPDATE/DELETE, find the appropriate junk attr now */
 					char		relkind;
@@ -2840,15 +2555,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
 							elog(ERROR, "could not find junk ctid column");
-
-						if (operation == CMD_MERGE &&
-							relkind == RELKIND_PARTITIONED_TABLE)
-						{
-							j->jf_otherJunkAttNo = ExecFindJunkAttribute(j, "tableoid");
-							if (!AttributeNumberIsValid(j->jf_otherJunkAttNo))
-								elog(ERROR, "could not find junk tableoid column");
-
-						}
 					}
 					else if (relkind == RELKIND_FOREIGN_TABLE)
 					{
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index a49015e7cb..08f6f67a15 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2420,9 +2420,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
 			else
 				res = SPI_OK_UPDATE;
 			break;
-		case CMD_MERGE:
-			res = SPI_OK_MERGE;
-			break;
 		default:
 			return SPI_ERROR_OPUNKNOWN;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 770ed3b1a8..c7293a60d7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -207,7 +207,6 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(partitioned_rels);
 	COPY_SCALAR_FIELD(partColsUpdated);
 	COPY_NODE_FIELD(resultRelations);
-	COPY_SCALAR_FIELD(mergeTargetRelation);
 	COPY_SCALAR_FIELD(resultRelIndex);
 	COPY_SCALAR_FIELD(rootResultRelIndex);
 	COPY_NODE_FIELD(plans);
@@ -223,8 +222,6 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(onConflictWhere);
 	COPY_SCALAR_FIELD(exclRelRTI);
 	COPY_NODE_FIELD(exclRelTlist);
-	COPY_NODE_FIELD(mergeSourceTargetList);
-	COPY_NODE_FIELD(mergeActionList);
 
 	return newnode;
 }
@@ -2980,9 +2977,6 @@ _copyQuery(const Query *from)
 	COPY_NODE_FIELD(setOperations);
 	COPY_NODE_FIELD(constraintDeps);
 	COPY_NODE_FIELD(withCheckOptions);
-	COPY_SCALAR_FIELD(mergeTarget_relation);
-	COPY_NODE_FIELD(mergeSourceTargetList);
-	COPY_NODE_FIELD(mergeActionList);
 	COPY_LOCATION_FIELD(stmt_location);
 	COPY_LOCATION_FIELD(stmt_len);
 
@@ -3046,34 +3040,6 @@ _copyUpdateStmt(const UpdateStmt *from)
 	return newnode;
 }
 
-static MergeStmt *
-_copyMergeStmt(const MergeStmt *from)
-{
-	MergeStmt  *newnode = makeNode(MergeStmt);
-
-	COPY_NODE_FIELD(relation);
-	COPY_NODE_FIELD(source_relation);
-	COPY_NODE_FIELD(join_condition);
-	COPY_NODE_FIELD(mergeActionList);
-
-	return newnode;
-}
-
-static MergeAction *
-_copyMergeAction(const MergeAction *from)
-{
-	MergeAction *newnode = makeNode(MergeAction);
-
-	COPY_SCALAR_FIELD(matched);
-	COPY_SCALAR_FIELD(commandType);
-	COPY_NODE_FIELD(condition);
-	COPY_NODE_FIELD(qual);
-	COPY_NODE_FIELD(stmt);
-	COPY_NODE_FIELD(targetList);
-
-	return newnode;
-}
-
 static SelectStmt *
 _copySelectStmt(const SelectStmt *from)
 {
@@ -5136,12 +5102,6 @@ copyObjectImpl(const void *from)
 		case T_UpdateStmt:
 			retval = _copyUpdateStmt(from);
 			break;
-		case T_MergeStmt:
-			retval = _copyMergeStmt(from);
-			break;
-		case T_MergeAction:
-			retval = _copyMergeAction(from);
-			break;
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5a0151eece..765b1be74b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -987,8 +987,6 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_NODE_FIELD(setOperations);
 	COMPARE_NODE_FIELD(constraintDeps);
 	COMPARE_NODE_FIELD(withCheckOptions);
-	COMPARE_NODE_FIELD(mergeSourceTargetList);
-	COMPARE_NODE_FIELD(mergeActionList);
 	COMPARE_LOCATION_FIELD(stmt_location);
 	COMPARE_LOCATION_FIELD(stmt_len);
 
@@ -1044,30 +1042,6 @@ _equalUpdateStmt(const UpdateStmt *a, const UpdateStmt *b)
 	return true;
 }
 
-static bool
-_equalMergeStmt(const MergeStmt *a, const MergeStmt *b)
-{
-	COMPARE_NODE_FIELD(relation);
-	COMPARE_NODE_FIELD(source_relation);
-	COMPARE_NODE_FIELD(join_condition);
-	COMPARE_NODE_FIELD(mergeActionList);
-
-	return true;
-}
-
-static bool
-_equalMergeAction(const MergeAction *a, const MergeAction *b)
-{
-	COMPARE_SCALAR_FIELD(matched);
-	COMPARE_SCALAR_FIELD(commandType);
-	COMPARE_NODE_FIELD(condition);
-	COMPARE_NODE_FIELD(qual);
-	COMPARE_NODE_FIELD(stmt);
-	COMPARE_NODE_FIELD(targetList);
-
-	return true;
-}
-
 static bool
 _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
 {
@@ -3259,12 +3233,6 @@ equal(const void *a, const void *b)
 		case T_UpdateStmt:
 			retval = _equalUpdateStmt(a, b);
 			break;
-		case T_MergeStmt:
-			retval = _equalMergeStmt(a, b);
-			break;
-		case T_MergeAction:
-			retval = _equalMergeAction(a, b);
-			break;
 		case T_SelectStmt:
 			retval = _equalSelectStmt(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 68e2cec66e..6c76c41ebe 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2146,16 +2146,6 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
-		case T_MergeAction:
-			{
-				MergeAction *action = (MergeAction *) node;
-
-				if (walker(action->targetList, context))
-					return true;
-				if (walker(action->qual, context))
-					return true;
-			}
-			break;
 		case T_JoinExpr:
 			{
 				JoinExpr   *join = (JoinExpr *) node;
@@ -2265,10 +2255,6 @@ query_tree_walker(Query *query,
 		return true;
 	if (walker((Node *) query->onConflict, context))
 		return true;
-	if (walker((Node *) query->mergeSourceTargetList, context))
-		return true;
-	if (walker((Node *) query->mergeActionList, context))
-		return true;
 	if (walker((Node *) query->returningList, context))
 		return true;
 	if (walker((Node *) query->jointree, context))
@@ -2946,18 +2932,6 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
-		case T_MergeAction:
-			{
-				MergeAction *action = (MergeAction *) node;
-				MergeAction *newnode;
-
-				FLATCOPY(newnode, action, MergeAction);
-				MUTATE(newnode->qual, action->qual, Node *);
-				MUTATE(newnode->targetList, action->targetList, List *);
-
-				return (Node *) newnode;
-			}
-			break;
 		case T_JoinExpr:
 			{
 				JoinExpr   *join = (JoinExpr *) node;
@@ -3109,8 +3083,6 @@ query_tree_mutator(Query *query,
 	MUTATE(query->targetList, query->targetList, List *);
 	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
 	MUTATE(query->onConflict, query->onConflict, OnConflictExpr *);
-	MUTATE(query->mergeSourceTargetList, query->mergeSourceTargetList, List *);
-	MUTATE(query->mergeActionList, query->mergeActionList, List *);
 	MUTATE(query->returningList, query->returningList, List *);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
@@ -3252,9 +3224,9 @@ query_or_expression_tree_mutator(Node *node,
  * boundaries: we descend to everything that's possibly interesting.
  *
  * Currently, the node type coverage here extends only to DML statements
- * (SELECT/INSERT/UPDATE/DELETE/MERGE) and nodes that can appear in them,
- * because this is used mainly during analysis of CTEs, and only DML
- * statements can appear in CTEs.
+ * (SELECT/INSERT/UPDATE/DELETE) and nodes that can appear in them, because
+ * this is used mainly during analysis of CTEs, and only DML statements can
+ * appear in CTEs.
  */
 bool
 raw_expression_tree_walker(Node *node,
@@ -3434,20 +3406,6 @@ raw_expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
-		case T_MergeStmt:
-			{
-				MergeStmt  *stmt = (MergeStmt *) node;
-
-				if (walker(stmt->relation, context))
-					return true;
-				if (walker(stmt->source_relation, context))
-					return true;
-				if (walker(stmt->join_condition, context))
-					return true;
-				if (walker(stmt->mergeActionList, context))
-					return true;
-			}
-			break;
 		case T_SelectStmt:
 			{
 				SelectStmt *stmt = (SelectStmt *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9ebea55048..f61ae03ac5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -375,7 +375,6 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_BOOL_FIELD(partColsUpdated);
 	WRITE_NODE_FIELD(resultRelations);
-	WRITE_INT_FIELD(mergeTargetRelation);
 	WRITE_INT_FIELD(resultRelIndex);
 	WRITE_INT_FIELD(rootResultRelIndex);
 	WRITE_NODE_FIELD(plans);
@@ -391,21 +390,6 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(onConflictWhere);
 	WRITE_UINT_FIELD(exclRelRTI);
 	WRITE_NODE_FIELD(exclRelTlist);
-	WRITE_NODE_FIELD(mergeSourceTargetList);
-	WRITE_NODE_FIELD(mergeActionList);
-}
-
-static void
-_outMergeAction(StringInfo str, const MergeAction *node)
-{
-	WRITE_NODE_TYPE("MERGEACTION");
-
-	WRITE_BOOL_FIELD(matched);
-	WRITE_ENUM_FIELD(commandType, CmdType);
-	WRITE_NODE_FIELD(condition);
-	WRITE_NODE_FIELD(qual);
-	/* We don't dump the stmt node */
-	WRITE_NODE_FIELD(targetList);
 }
 
 static void
@@ -2130,7 +2114,6 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
 	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_BOOL_FIELD(partColsUpdated);
 	WRITE_NODE_FIELD(resultRelations);
-	WRITE_INT_FIELD(mergeTargetRelation);
 	WRITE_NODE_FIELD(subpaths);
 	WRITE_NODE_FIELD(subroots);
 	WRITE_NODE_FIELD(withCheckOptionLists);
@@ -2138,8 +2121,6 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(onconflict);
 	WRITE_INT_FIELD(epqParam);
-	WRITE_NODE_FIELD(mergeSourceTargetList);
-	WRITE_NODE_FIELD(mergeActionList);
 }
 
 static void
@@ -2961,9 +2942,6 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_NODE_FIELD(setOperations);
 	WRITE_NODE_FIELD(constraintDeps);
 	/* withCheckOptions intentionally omitted, see comment in parsenodes.h */
-	WRITE_INT_FIELD(mergeTarget_relation);
-	WRITE_NODE_FIELD(mergeSourceTargetList);
-	WRITE_NODE_FIELD(mergeActionList);
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_LOCATION_FIELD(stmt_len);
 }
@@ -3679,9 +3657,6 @@ outNode(StringInfo str, const void *obj)
 			case T_ModifyTable:
 				_outModifyTable(str, obj);
 				break;
-			case T_MergeAction:
-				_outMergeAction(str, obj);
-				break;
 			case T_Append:
 				_outAppend(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 694bb3fc40..d02d4ec5b7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -270,9 +270,6 @@ _readQuery(void)
 	READ_NODE_FIELD(setOperations);
 	READ_NODE_FIELD(constraintDeps);
 	/* withCheckOptions intentionally omitted, see comment in parsenodes.h */
-	READ_INT_FIELD(mergeTarget_relation);
-	READ_NODE_FIELD(mergeSourceTargetList);
-	READ_NODE_FIELD(mergeActionList);
 	READ_LOCATION_FIELD(stmt_location);
 	READ_LOCATION_FIELD(stmt_len);
 
@@ -1579,7 +1576,6 @@ _readModifyTable(void)
 	READ_NODE_FIELD(partitioned_rels);
 	READ_BOOL_FIELD(partColsUpdated);
 	READ_NODE_FIELD(resultRelations);
-	READ_INT_FIELD(mergeTargetRelation);
 	READ_INT_FIELD(resultRelIndex);
 	READ_INT_FIELD(rootResultRelIndex);
 	READ_NODE_FIELD(plans);
@@ -1595,8 +1591,6 @@ _readModifyTable(void)
 	READ_NODE_FIELD(onConflictWhere);
 	READ_UINT_FIELD(exclRelRTI);
 	READ_NODE_FIELD(exclRelTlist);
-	READ_NODE_FIELD(mergeSourceTargetList);
-	READ_NODE_FIELD(mergeActionList);
 
 	READ_DONE();
 }
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 99d0736029..ccdd5cdaba 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -288,13 +288,9 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
 				 CmdType operation, bool canSetTag,
 				 Index nominalRelation, List *partitioned_rels,
 				 bool partColsUpdated,
-				 List *resultRelations,
-				 Index mergeTargetRelation,
-				 List *subplans,
+				 List *resultRelations, List *subplans,
 				 List *withCheckOptionLists, List *returningLists,
-				 List *rowMarks, OnConflictExpr *onconflict,
-				 List *mergeSourceTargetList,
-				 List *mergeActionList, int epqParam);
+				 List *rowMarks, OnConflictExpr *onconflict, int epqParam);
 static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
 						 GatherMergePath *best_path);
 
@@ -2450,14 +2446,11 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->partitioned_rels,
 							best_path->partColsUpdated,
 							best_path->resultRelations,
-							best_path->mergeTargetRelation,
 							subplans,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->rowMarks,
 							best_path->onconflict,
-							best_path->mergeSourceTargetList,
-							best_path->mergeActionList,
 							best_path->epqParam);
 
 	copy_generic_path_info(&plan->plan, &best_path->path);
@@ -6524,13 +6517,9 @@ make_modifytable(PlannerInfo *root,
 				 CmdType operation, bool canSetTag,
 				 Index nominalRelation, List *partitioned_rels,
 				 bool partColsUpdated,
-				 List *resultRelations,
-				 Index mergeTargetRelation,
-				 List *subplans,
+				 List *resultRelations, List *subplans,
 				 List *withCheckOptionLists, List *returningLists,
-				 List *rowMarks, OnConflictExpr *onconflict,
-				 List *mergeSourceTargetList,
-				 List *mergeActionList, int epqParam)
+				 List *rowMarks, OnConflictExpr *onconflict, int epqParam)
 {
 	ModifyTable *node = makeNode(ModifyTable);
 	List	   *fdw_private_list;
@@ -6556,7 +6545,6 @@ make_modifytable(PlannerInfo *root,
 	node->partitioned_rels = partitioned_rels;
 	node->partColsUpdated = partColsUpdated;
 	node->resultRelations = resultRelations;
-	node->mergeTargetRelation = mergeTargetRelation;
 	node->resultRelIndex = -1;	/* will be set correctly in setrefs.c */
 	node->rootResultRelIndex = -1;	/* will be set correctly in setrefs.c */
 	node->plans = subplans;
@@ -6589,8 +6577,6 @@ make_modifytable(PlannerInfo *root,
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
-	node->mergeSourceTargetList = mergeSourceTargetList;
-	node->mergeActionList = mergeActionList;
 	node->epqParam = epqParam;
 
 	/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 15c8d34c70..53ed6f8a17 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -794,24 +794,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		/* exclRelTlist contains only Vars, so no preprocessing needed */
 	}
 
-	foreach(l, parse->mergeActionList)
-	{
-		MergeAction *action = (MergeAction *) lfirst(l);
-
-		action->targetList = (List *)
-			preprocess_expression(root,
-								  (Node *) action->targetList,
-								  EXPRKIND_TARGET);
-		action->qual =
-			preprocess_expression(root,
-								  (Node *) action->qual,
-								  EXPRKIND_QUAL);
-	}
-
-	parse->mergeSourceTargetList = (List *)
-		preprocess_expression(root, (Node *) parse->mergeSourceTargetList,
-							  EXPRKIND_TARGET);
-
 	root->append_rel_list = (List *)
 		preprocess_expression(root, (Node *) root->append_rel_list,
 							  EXPRKIND_APPINFO);
@@ -1553,7 +1535,6 @@ inheritance_planner(PlannerInfo *root)
 									 subroot->parse->returningList);
 
 		Assert(!parse->onConflict);
-		Assert(parse->mergeActionList == NIL);
 	}
 
 	/* Result path must go into outer query's FINAL upperrel */
@@ -1612,15 +1593,12 @@ inheritance_planner(PlannerInfo *root)
 									 partitioned_rels,
 									 partColsUpdated,
 									 resultRelations,
-									 0,
 									 subpaths,
 									 subroots,
 									 withCheckOptionLists,
 									 returningLists,
 									 rowMarks,
 									 NULL,
-									 NULL,
-									 NULL,
 									 SS_assign_special_param(root)));
 }
 
@@ -2151,8 +2129,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 		}
 
 		/*
-		 * If this is an INSERT/UPDATE/DELETE/MERGE, and we're not being
-		 * called from inheritance_planner, add the ModifyTable node.
+		 * If this is an INSERT/UPDATE/DELETE, and we're not being called from
+		 * inheritance_planner, add the ModifyTable node.
 		 */
 		if (parse->commandType != CMD_SELECT && !inheritance_update)
 		{
@@ -2192,15 +2170,12 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										NIL,
 										false,
 										list_make1_int(parse->resultRelation),
-										parse->mergeTarget_relation,
 										list_make1(path),
 										list_make1(root),
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
 										parse->onConflict,
-										parse->mergeSourceTargetList,
-										parse->mergeActionList,
 										SS_assign_special_param(root));
 		}
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index cd540a0df5..69dd327f0c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -851,60 +851,6 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 						fix_scan_list(root, splan->exclRelTlist, rtoffset);
 				}
 
-				/*
-				 * The MERGE produces the target rows by performing a right
-				 * join between the target relation and the source relation
-				 * (which could be a plain relation or a subquery). The INSERT
-				 * and UPDATE actions of the MERGE requires access to the
-				 * columns from the source relation. We arrange things so that
-				 * the source relation attributes are available as INNER_VAR
-				 * and the target relation attributes are available from the
-				 * scan tuple.
-				 */
-				if (splan->mergeActionList != NIL)
-				{
-					/*
-					 * mergeSourceTargetList is already setup correctly to
-					 * include all Vars coming from the source relation. So we
-					 * fix the targetList of individual action nodes by
-					 * ensuring that the source relation Vars are referenced
-					 * as INNER_VAR. Note that for this to work correctly,
-					 * during execution, the ecxt_innertuple must be set to
-					 * the tuple obtained from the source relation.
-					 *
-					 * We leave the Vars from the result relation (i.e. the
-					 * target relation) unchanged i.e. those Vars would be
-					 * picked from the scan slot. So during execution, we must
-					 * ensure that ecxt_scantuple is setup correctly to refer
-					 * to the tuple from the target relation.
-					 */
-
-					indexed_tlist *itlist;
-
-					itlist = build_tlist_index(splan->mergeSourceTargetList);
-
-					splan->mergeTargetRelation += rtoffset;
-
-					foreach(l, splan->mergeActionList)
-					{
-						MergeAction *action = (MergeAction *) lfirst(l);
-
-						/* Fix targetList of each action. */
-						action->targetList = fix_join_expr(root,
-								action->targetList,
-								NULL, itlist,
-								linitial_int(splan->resultRelations),
-								rtoffset);
-
-						/* Fix quals too. */
-						action->qual = (Node *) fix_join_expr(root,
-								(List *) action->qual,
-								NULL, itlist,
-								linitial_int(splan->resultRelations),
-								rtoffset);
-					}
-				}
-
 				splan->nominalRelation += rtoffset;
 				splan->exclRelRTI += rtoffset;
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 8a87cfd14a..8603feef2b 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -118,46 +118,6 @@ preprocess_targetlist(PlannerInfo *root)
 		tlist = expand_targetlist(tlist, command_type,
 								  result_relation, target_relation);
 
-	if (command_type == CMD_MERGE)
-	{
-		ListCell   *l;
-
-		/*
-		 * For MERGE, add any junk column(s) needed to allow the executor to
-		 * identify the rows to be updated or deleted, with different
-		 * handling for partitioned tables.
-		 */
-		rewriteTargetListMerge(parse, target_relation);
-
-		/*
-		 * For MERGE command, handle targetlist of each MergeAction separately.
-		 * Give the same treatment to MergeAction->targetList as we would have
-		 * given to a regular INSERT/UPDATE/DELETE.
-		 */
-		foreach(l, parse->mergeActionList)
-		{
-			MergeAction *action = (MergeAction *) lfirst(l);
-
-			switch (action->commandType)
-			{
-				case CMD_INSERT:
-				case CMD_UPDATE:
-					action->targetList = expand_targetlist(action->targetList,
-														   action->commandType,
-														   result_relation,
-														   target_relation);
-					break;
-				case CMD_DELETE:
-					break;
-				case CMD_NOTHING:
-					break;
-				default:
-					elog(ERROR, "unknown action in MERGE WHEN clause");
-
-			}
-		}
-	}
-
 	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
 	 * for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
@@ -388,7 +348,6 @@ expand_targetlist(List *tlist, int command_type,
 													  true /* byval */ );
 					}
 					break;
-				case CMD_MERGE:
 				case CMD_UPDATE:
 					if (!att_tup->attisdropped)
 					{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 416b3f9578..22133fcf12 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3284,21 +3284,17 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
  * 'onconflict' is the ON CONFLICT clause, or NULL
  * 'epqParam' is the ID of Param for EvalPlanQual re-eval
- * 'mergeActionList' is a list of MERGE actions
  */
 ModifyTablePath *
 create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						CmdType operation, bool canSetTag,
 						Index nominalRelation, List *partitioned_rels,
 						bool partColsUpdated,
-						List *resultRelations,
-						Index mergeTargetRelation,
-						List *subpaths,
+						List *resultRelations, List *subpaths,
 						List *subroots,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
-						List *mergeSourceTargetList,
-						List *mergeActionList, int epqParam)
+						int epqParam)
 {
 	ModifyTablePath *pathnode = makeNode(ModifyTablePath);
 	double		total_size;
@@ -3363,7 +3359,6 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->partitioned_rels = list_copy(partitioned_rels);
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
-	pathnode->mergeTargetRelation = mergeTargetRelation;
 	pathnode->subpaths = subpaths;
 	pathnode->subroots = subroots;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
@@ -3371,8 +3366,6 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->rowMarks = rowMarks;
 	pathnode->onconflict = onconflict;
 	pathnode->epqParam = epqParam;
-	pathnode->mergeSourceTargetList = mergeSourceTargetList;
-	pathnode->mergeActionList = mergeActionList;
 
 	return pathnode;
 }
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8a6baa7bea..0231f8bf7c 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1835,10 +1835,6 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 				 trigDesc->trig_delete_before_row))
 				result = true;
 			break;
-			/* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
-		case CMD_MERGE:
-			result = false;
-			break;
 		default:
 			elog(ERROR, "unrecognized CmdType: %d", (int) event);
 			break;
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 95fdf0b973..f14febdbda 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -14,7 +14,7 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
 
 OBJS= analyze.o gram.o scan.o parser.o \
       parse_agg.o parse_clause.o parse_coerce.o parse_collate.o parse_cte.o \
-      parse_enr.o parse_expr.o parse_func.o parse_merge.o parse_node.o parse_oper.o \
+      parse_enr.o parse_expr.o parse_func.o parse_node.o parse_oper.o \
       parse_param.o parse_relation.o parse_target.o parse_type.o \
       parse_utilcmd.o scansup.o
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7eb9544efe..a4b5aaef44 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -38,7 +38,6 @@
 #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"
@@ -54,6 +53,9 @@ 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);
@@ -66,6 +68,8 @@ static void determineRecursiveColTypes(ParseState *pstate,
 						   Node *larg, List *nrtargetlist);
 static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
 static List *transformReturningList(ParseState *pstate, List *returningList);
+static List *transformUpdateTargetList(ParseState *pstate,
+						  List *targetList);
 static Query *transformDeclareCursorStmt(ParseState *pstate,
 						   DeclareCursorStmt *stmt);
 static Query *transformExplainStmt(ParseState *pstate,
@@ -263,7 +267,6 @@ 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:
@@ -288,10 +291,6 @@ 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;
@@ -367,7 +366,6 @@ analyze_requires_snapshot(RawStmt *parseTree)
 		case T_InsertStmt:
 		case T_DeleteStmt:
 		case T_UpdateStmt:
-		case T_MergeStmt:
 		case T_SelectStmt:
 			result = true;
 			break;
@@ -898,7 +896,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
  */
-List *
+static List *
 transformInsertRow(ParseState *pstate, List *exprlist,
 				   List *stmtcols, List *icolumns, List *attrnos,
 				   bool strip_indirection)
@@ -2262,9 +2260,9 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 
 /*
  * transformUpdateTargetList -
- *	handle SET clause in UPDATE/MERGE/INSERT ... ON CONFLICT UPDATE
+ *	handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE
  */
-List *
+static List *
 transformUpdateTargetList(ParseState *pstate, List *origTlist)
 {
 	List	   *tlist = NIL;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 583ee321e1..cd5ba2d4d8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -282,7 +282,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		CreateMatViewStmt RefreshMatViewStmt CreateAmStmt
 		CreatePublicationStmt AlterPublicationStmt
 		CreateSubscriptionStmt AlterSubscriptionStmt DropSubscriptionStmt
-		MergeStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -585,10 +584,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>		hash_partbound partbound_datum_list range_datum_list
 %type <defelt>		hash_partbound_elem
 
-%type <node>	merge_when_clause opt_and_condition
-%type <list>	merge_when_list
-%type <node>	merge_update merge_delete merge_insert
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -656,8 +651,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
-	MINUTE_P MINVALUE MODE MONTH_P MOVE
+	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
@@ -926,7 +920,6 @@ stmt :
 			| RefreshMatViewStmt
 			| LoadStmt
 			| LockStmt
-			| MergeStmt
 			| NotifyStmt
 			| PrepareStmt
 			| ReassignOwnedStmt
@@ -10667,7 +10660,6 @@ ExplainableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt
-			| MergeStmt
 			| DeclareCursorStmt
 			| CreateAsStmt
 			| CreateMatViewStmt
@@ -10730,7 +10722,6 @@ PreparableStmt:
 			| InsertStmt
 			| UpdateStmt
 			| DeleteStmt					/* by default all are $$=$1 */
-			| MergeStmt
 		;
 
 /*****************************************************************************
@@ -11097,151 +11088,6 @@ set_target_list:
 		;
 
 
-/*****************************************************************************
- *
- *		QUERY:
- *				MERGE STATEMENTS
- *
- *****************************************************************************/
-
-MergeStmt:
-			MERGE INTO relation_expr_opt_alias
-			USING table_ref
-			ON a_expr
-			merge_when_list
-				{
-					MergeStmt *m = makeNode(MergeStmt);
-
-					m->relation = $3;
-					m->source_relation = $5;
-					m->join_condition = $7;
-					m->mergeActionList = $8;
-
-					$$ = (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_and_condition THEN merge_update
-				{
-					MergeAction *m = makeNode(MergeAction);
-
-					m->matched = true;
-					m->commandType = CMD_UPDATE;
-					m->condition = $3;
-					m->stmt = $5;
-
-					$$ = (Node *)m;
-				}
-			| WHEN MATCHED opt_and_condition THEN merge_delete
-				{
-					MergeAction *m = makeNode(MergeAction);
-
-					m->matched = true;
-					m->commandType = CMD_DELETE;
-					m->condition = $3;
-					m->stmt = $5;
-
-					$$ = (Node *)m;
-				}
-			| WHEN NOT MATCHED opt_and_condition THEN merge_insert
-				{
-					MergeAction *m = makeNode(MergeAction);
-
-					m->matched = false;
-					m->commandType = CMD_INSERT;
-					m->condition = $4;
-					m->stmt = $6;
-
-					$$ = (Node *)m;
-				}
-			| WHEN NOT MATCHED opt_and_condition THEN DO NOTHING
-				{
-					MergeAction *m = makeNode(MergeAction);
-
-					m->matched = false;
-					m->commandType = CMD_NOTHING;
-					m->condition = $4;
-					m->stmt = NULL;
-
-					$$ = (Node *)m;
-				}
-			;
-
-opt_and_condition:
-			AND a_expr 				{ $$ = $2; }
-			| 			 			{ $$ = NULL; }
-			;
-
-merge_delete:
-			DELETE_P
-				{
-					DeleteStmt *n = makeNode(DeleteStmt);
-					$$ = (Node *)n;
-				}
-			;
-
-merge_update:
-			UPDATE SET set_clause_list
-				{
-					UpdateStmt *n = makeNode(UpdateStmt);
-					n->targetList = $3;
-
-					$$ = (Node *)n;
-				}
-			;
-
-merge_insert:
-			INSERT values_clause
-				{
-					InsertStmt *n = makeNode(InsertStmt);
-					n->cols = NIL;
-					n->selectStmt = $2;
-
-					$$ = (Node *)n;
-				}
-			| INSERT OVERRIDING override_kind VALUE_P values_clause
-				{
-					InsertStmt *n = makeNode(InsertStmt);
-					n->cols = NIL;
-					n->override = $3;
-					n->selectStmt = $5;
-
-					$$ = (Node *)n;
-				}
-			| INSERT '(' insert_column_list ')' values_clause
-				{
-					InsertStmt *n = makeNode(InsertStmt);
-					n->cols = $3;
-					n->selectStmt = $5;
-
-					$$ = (Node *)n;
-				}
-			| INSERT '(' insert_column_list ')' OVERRIDING override_kind VALUE_P values_clause
-				{
-					InsertStmt *n = makeNode(InsertStmt);
-					n->cols = $3;
-					n->override = $6;
-					n->selectStmt = $8;
-
-					$$ = (Node *)n;
-				}
-			| INSERT DEFAULT VALUES
-				{
-					InsertStmt *n = makeNode(InsertStmt);
-					n->cols = NIL;
-					n->selectStmt = NULL;
-
-					$$ = (Node *)n;
-				}
-			;
-
 /*****************************************************************************
  *
  *		QUERY:
@@ -15242,10 +15088,8 @@ unreserved_keyword:
 			| LOGGED
 			| MAPPING
 			| MATCH
-			| MATCHED
 			| MATERIALIZED
 			| MAXVALUE
-			| MERGE
 			| METHOD
 			| MINUTE_P
 			| MINVALUE
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 544e7300b8..377a7ed6d0 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -455,13 +455,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 		case EXPR_KIND_VALUES_SINGLE:
 			errkind = true;
 			break;
-		case EXPR_KIND_MERGE_WHEN_AND:
-			if (isAgg)
-				err = _("aggregate functions are not allowed in WHEN AND conditions");
-			else
-				err = _("grouping operations are not allowed in WHEN AND conditions");
-
-			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
 		case EXPR_KIND_DOMAIN_CHECK:
 			if (isAgg)
@@ -880,9 +873,6 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_VALUES_SINGLE:
 			errkind = true;
 			break;
-		case EXPR_KIND_MERGE_WHEN_AND:
-			err = _("window functions are not allowed in WHEN AND conditions");
-			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
 		case EXPR_KIND_DOMAIN_CHECK:
 			err = _("window functions are not allowed in check constraints");
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 3cb761b4ed..3a02307bd9 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -76,6 +76,9 @@ static RangeTblEntry *transformRangeTableFunc(ParseState *pstate,
 						RangeTableFunc *t);
 static TableSampleClause *transformRangeTableSample(ParseState *pstate,
 						  RangeTableSample *rts);
+static Node *transformFromClauseItem(ParseState *pstate, Node *n,
+						RangeTblEntry **top_rte, int *top_rti,
+						List **namespace);
 static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
 				   Var *l_colvar, Var *r_colvar);
 static ParseNamespaceItem *makeNamespaceItem(RangeTblEntry *rte,
@@ -136,7 +139,6 @@ transformFromClause(ParseState *pstate, List *frmList)
 		n = transformFromClauseItem(pstate, n,
 									&rte,
 									&rtindex,
-									NULL, NULL,
 									&namespace);
 
 		checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace);
@@ -1094,20 +1096,13 @@ getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
  *
  * *top_rti: receives the rangetable index of top_rte.  (Ditto.)
  *
- * *right_rte: receives the RTE corresponding to the right side of the
- * jointree. Only MERGE really needs to know about this and only MERGE passes a
- * non-NULL pointer.
- *
- * *right_rti: receives the rangetable index of the right_rte.
- *
  * *namespace: receives a List of ParseNamespaceItems for the RTEs exposed
  * as table/column names by this item.  (The lateral_only flags in these items
  * are indeterminate and should be explicitly set by the caller before use.)
  */
-Node *
+static Node *
 transformFromClauseItem(ParseState *pstate, Node *n,
 						RangeTblEntry **top_rte, int *top_rti,
-						RangeTblEntry **right_rte, int *right_rti,
 						List **namespace)
 {
 	if (IsA(n, RangeVar))
@@ -1199,7 +1194,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 
 		/* Recursively transform the contained relation */
 		rel = transformFromClauseItem(pstate, rts->relation,
-									  top_rte, top_rti, NULL, NULL, namespace);
+									  top_rte, top_rti, namespace);
 		/* Currently, grammar could only return a RangeVar as contained rel */
 		rtr = castNode(RangeTblRef, rel);
 		rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
@@ -1227,7 +1222,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		List	   *l_namespace,
 				   *r_namespace,
 				   *my_namespace,
-				   *save_namespace,
 				   *l_colnames,
 				   *r_colnames,
 				   *res_colnames,
@@ -1246,7 +1240,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		j->larg = transformFromClauseItem(pstate, j->larg,
 										  &l_rte,
 										  &l_rtindex,
-										  NULL, NULL,
 										  &l_namespace);
 
 		/*
@@ -1270,34 +1263,12 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		sv_namespace_length = list_length(pstate->p_namespace);
 		pstate->p_namespace = list_concat(pstate->p_namespace, l_namespace);
 
-		/*
-		 * If we are running MERGE, don't make the other RTEs visible while
-		 * parsing the source relation. It mustn't see them.
-		 *
-		 * Currently, only MERGE passes non-NULL value for right_rte, so we
-		 * can safely deduce if we're running MERGE or not by just looking at
-		 * the right_rte. If that ever changes, we should look at other means
-		 * to find that.
-		 */
-		if (right_rte)
-		{
-			save_namespace = pstate->p_namespace;
-			pstate->p_namespace = NIL;
-		}
-
 		/* And now we can process the RHS */
 		j->rarg = transformFromClauseItem(pstate, j->rarg,
 										  &r_rte,
 										  &r_rtindex,
-										  NULL, NULL,
 										  &r_namespace);
 
-		/*
-		 * And now restore the namespace again so that join-quals can see it.
-		 */
-		if (right_rte)
-			pstate->p_namespace = save_namespace;
-
 		/* Remove the left-side RTEs from the namespace list again */
 		pstate->p_namespace = list_truncate(pstate->p_namespace,
 											sv_namespace_length);
@@ -1324,12 +1295,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		expandRTE(r_rte, r_rtindex, 0, -1, false,
 				  &r_colnames, &r_colvars);
 
-		if (right_rte)
-			*right_rte = r_rte;
-
-		if (right_rti)
-			*right_rti = r_rtindex;
-
 		/*
 		 * Natural join does not explicitly specify columns; must generate
 		 * columns to join. Need to run through the list of columns from each
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 51c73c4018..6d34245083 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -485,7 +485,6 @@ 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 38fbe3366f..385e54a9b6 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1818,7 +1818,6 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
 		case EXPR_KIND_VALUES_SINGLE:
-		case EXPR_KIND_MERGE_WHEN_AND:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3476,8 +3475,6 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "PARTITION BY";
 		case EXPR_KIND_CALL_ARGUMENT:
 			return "CALL";
-		case EXPR_KIND_MERGE_WHEN_AND:
-			return "MERGE WHEN AND";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 615aee6d15..ea5d5212b4 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2277,9 +2277,6 @@ 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_AND:
-			err = _("set-returning functions are not allowed in WHEN AND 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_relation.c b/src/backend/parser/parse_relation.c
index f7e11f969c..053ae02c9f 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -728,16 +728,6 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, const char *colname,
 							colname),
 					 parser_errposition(pstate, location)));
 
-		/* In MERGE WHEN AND condition, no system column is allowed except tableOid or OID */
-		if (pstate->p_expr_kind == EXPR_KIND_MERGE_WHEN_AND &&
-			attnum < InvalidAttrNumber &&
-			!(attnum == TableOidAttributeNumber || attnum == ObjectIdAttributeNumber))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
-					 errmsg("system column \"%s\" reference in WHEN AND condition is invalid",
-							colname),
-					 parser_errposition(pstate, location)));
-
 		if (attnum != InvalidAttrNumber)
 		{
 			/* now check to see if column actually is defined */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 98239f569a..88140bc687 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1377,57 +1377,6 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	}
 }
 
-void
-rewriteTargetListMerge(Query *parsetree, Relation target_relation)
-{
-	Var		   *var = NULL;
-	const char *attrname;
-	TargetEntry *tle;
-
-	Assert(target_relation->rd_rel->relkind == RELKIND_RELATION ||
-		   target_relation->rd_rel->relkind == RELKIND_MATVIEW ||
-		   target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
-
-	/*
-	 * Emit CTID so that executor can find the row to update or delete.
-	 */
-	var = makeVar(parsetree->mergeTarget_relation,
-				  SelfItemPointerAttributeNumber,
-				  TIDOID,
-				  -1,
-				  InvalidOid,
-				  0);
-
-	attrname = "ctid";
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
-
-	/*
-	 * If we are dealing with partitioned table, then emit TABLEOID so that
-	 * executor can find the partition the row belongs to.
-	 */
-	if (target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		var = makeVar(parsetree->mergeTarget_relation,
-				TableOidAttributeNumber,
-				OIDOID,
-				-1,
-				InvalidOid,
-				0);
-
-		attrname = "tableoid";
-		tle = makeTargetEntry((Expr *) var,
-				list_length(parsetree->targetList) + 1,
-				pstrdup(attrname),
-				true);
-
-		parsetree->targetList = lappend(parsetree->targetList, tle);
-	}
-}
 
 /*
  * matchLocks -
@@ -3382,7 +3331,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		}
 		else if (event == CMD_UPDATE)
 		{
-			Assert(parsetree->override == OVERRIDING_NOT_SET);
 			parsetree->targetList =
 				rewriteTargetListIU(parsetree->targetList,
 									parsetree->commandType,
@@ -3390,50 +3338,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 									rt_entry_relation,
 									parsetree->resultRelation, NULL);
 		}
-		else if (event == CMD_MERGE)
-		{
-			Assert(parsetree->override == OVERRIDING_NOT_SET);
-
-			/*
-			 * Rewrite each action targetlist separately
-			 */
-			foreach(lc1, parsetree->mergeActionList)
-			{
-				MergeAction *action = (MergeAction *) lfirst(lc1);
-
-				switch (action->commandType)
-				{
-					case CMD_NOTHING:
-					case CMD_DELETE:	/* Nothing to do here */
-						break;
-					case CMD_UPDATE:
-						action->targetList =
-							rewriteTargetListIU(action->targetList,
-												action->commandType,
-												parsetree->override,
-												rt_entry_relation,
-												parsetree->resultRelation,
-												NULL);
-						break;
-					case CMD_INSERT:
-						{
-							InsertStmt *istmt = (InsertStmt *) action->stmt;
-
-							action->targetList =
-								rewriteTargetListIU(action->targetList,
-													action->commandType,
-													istmt->override,
-													rt_entry_relation,
-													parsetree->resultRelation,
-													NULL);
-						}
-						break;
-					default:
-						elog(ERROR, "unrecognized commandType: %d", action->commandType);
-						break;
-				}
-			}
-		}
 		else if (event == CMD_DELETE)
 		{
 			/* Nothing to do here */
@@ -3447,20 +3351,13 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		locks = matchLocks(event, rt_entry_relation->rd_rules,
 						   result_relation, parsetree, &hasUpdate);
 
-		/*
-		 * XXX MERGE doesn't support write rules because they would violate
-		 * the SQL Standard spec and would be unclear how they should work.
-		 */
-		if (event == CMD_MERGE)
-			product_queries = NIL;
-		else
-			product_queries = fireRules(parsetree,
-										result_relation,
-										event,
-										locks,
-										&instead,
-										&returning,
-										&qual_product);
+		product_queries = fireRules(parsetree,
+									result_relation,
+									event,
+									locks,
+									&instead,
+									&returning,
+									&qual_product);
 
 		/*
 		 * If there were no INSTEAD rules, and the target relation is a view
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index 6e85886e64..ce77a18bc9 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -379,95 +379,6 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 		}
 	}
 
-	/*
-	 * FOR MERGE, we fetch policies for UPDATE, DELETE and INSERT (and ALL)
-	 * and set them up so that we can enforce the appropriate policy depending
-	 * on the final action we take.
-	 *
-	 * We don't fetch the SELECT policies since they are correctly applied to
-	 * the root->mergeTarget_relation. The target rows are selected after
-	 * joining the mergeTarget_relation and the source relation and hence it's
-	 * enough to apply SELECT policies to the mergeTarget_relation.
-	 *
-	 * We don't push the UPDATE/DELETE USING quals to the RTE because we don't
-	 * really want to apply them while scanning the relation since we don't
-	 * know whether we will be doing a UPDATE or a DELETE at the end. We apply
-	 * the respective policy once we decide the final action on the target
-	 * tuple.
-	 *
-	 * XXX We are setting up USING quals as WITH CHECK. If RLS prohibits
-	 * UPDATE/DELETE on the target row, we shall throw an error instead of
-	 * silently ignoring the row. This is different than how normal
-	 * UPDATE/DELETE works and more in line with INSERT ON CONFLICT DO UPDATE
-	 * handling.
-	 */
-	if (commandType == CMD_MERGE)
-	{
-		List	   *merge_permissive_policies;
-		List	   *merge_restrictive_policies;
-
-		/*
-		 * Fetch the UPDATE policies and set them up to execute on the
-		 * existing target row before doing UPDATE.
-		 */
-		get_policies_for_relation(rel, CMD_UPDATE, user_id,
-								  &merge_permissive_policies,
-								  &merge_restrictive_policies);
-
-		/*
-		 * WCO_RLS_MERGE_UPDATE_CHECK is used to check UPDATE USING quals on
-		 * the existing target row.
-		 */
-		add_with_check_options(rel, rt_index,
-							   WCO_RLS_MERGE_UPDATE_CHECK,
-							   merge_permissive_policies,
-							   merge_restrictive_policies,
-							   withCheckOptions,
-							   hasSubLinks,
-							   true);
-
-		/*
-		 * Same with DELETE policies.
-		 */
-		get_policies_for_relation(rel, CMD_DELETE, user_id,
-								  &merge_permissive_policies,
-								  &merge_restrictive_policies);
-
-		add_with_check_options(rel, rt_index,
-							   WCO_RLS_MERGE_DELETE_CHECK,
-							   merge_permissive_policies,
-							   merge_restrictive_policies,
-							   withCheckOptions,
-							   hasSubLinks,
-							   true);
-
-		/*
-		 * No special handling is required for INSERT policies. They will be
-		 * checked and enforced during ExecInsert(). But we must add them to
-		 * withCheckOptions.
-		 */
-		get_policies_for_relation(rel, CMD_INSERT, user_id,
-								  &merge_permissive_policies,
-								  &merge_restrictive_policies);
-
-		add_with_check_options(rel, rt_index,
-							   WCO_RLS_INSERT_CHECK,
-							   merge_permissive_policies,
-							   merge_restrictive_policies,
-							   withCheckOptions,
-							   hasSubLinks,
-							   false);
-
-		/* Enforce the WITH CHECK clauses of the UPDATE policies */
-		add_with_check_options(rel, rt_index,
-							   WCO_RLS_UPDATE_CHECK,
-							   merge_permissive_policies,
-							   merge_restrictive_policies,
-							   withCheckOptions,
-							   hasSubLinks,
-							   false);
-	}
-
 	heap_close(rel, NoLock);
 
 	/*
@@ -527,14 +438,6 @@ get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id,
 					if (policy->polcmd == ACL_DELETE_CHR)
 						cmd_matches = true;
 					break;
-				case CMD_MERGE:
-
-					/*
-					 * We do not support a separate policy for MERGE command.
-					 * Instead it derives from the policies defined for other
-					 * commands.
-					 */
-					break;
 				default:
 					elog(ERROR, "unrecognized policy command type %d",
 						 (int) cmd);
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 50f852a4aa..66cc5c35c6 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -193,11 +193,6 @@ ProcessQuery(PlannedStmt *plan,
 						 "DELETE " UINT64_FORMAT,
 						 queryDesc->estate->es_processed);
 				break;
-			case CMD_MERGE:
-				snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
-						 "MERGE " UINT64_FORMAT,
-						 queryDesc->estate->es_processed);
-				break;
 			default:
 				strcpy(completionTag, "???");
 				break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 84f2591736..b2dc9d18ea 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -110,7 +110,6 @@ CommandIsReadOnly(PlannedStmt *pstmt)
 		case CMD_UPDATE:
 		case CMD_INSERT:
 		case CMD_DELETE:
-		case CMD_MERGE:
 			return false;
 		case CMD_UTILITY:
 			/* For now, treat all utility commands as read/write */
@@ -1833,8 +1832,6 @@ QueryReturnsTuples(Query *parsetree)
 		case CMD_SELECT:
 			/* returns tuples */
 			return true;
-		case CMD_MERGE:
-			return false;
 		case CMD_INSERT:
 		case CMD_UPDATE:
 		case CMD_DELETE:
@@ -2079,10 +2076,6 @@ CreateCommandTag(Node *parsetree)
 			tag = "UPDATE";
 			break;
 
-		case T_MergeStmt:
-			tag = "MERGE";
-			break;
-
 		case T_SelectStmt:
 			tag = "SELECT";
 			break;
@@ -2826,9 +2819,6 @@ CreateCommandTag(Node *parsetree)
 					case CMD_DELETE:
 						tag = "DELETE";
 						break;
-					case CMD_MERGE:
-						tag = "MERGE";
-						break;
 					case CMD_UTILITY:
 						tag = CreateCommandTag(stmt->utilityStmt);
 						break;
@@ -2889,9 +2879,6 @@ CreateCommandTag(Node *parsetree)
 					case CMD_DELETE:
 						tag = "DELETE";
 						break;
-					case CMD_MERGE:
-						tag = "MERGE";
-						break;
 					case CMD_UTILITY:
 						tag = CreateCommandTag(stmt->utilityStmt);
 						break;
@@ -2940,7 +2927,6 @@ GetCommandLogLevel(Node *parsetree)
 		case T_InsertStmt:
 		case T_DeleteStmt:
 		case T_UpdateStmt:
-		case T_MergeStmt:
 			lev = LOGSTMT_MOD;
 			break;
 
@@ -3380,7 +3366,6 @@ GetCommandLogLevel(Node *parsetree)
 					case CMD_UPDATE:
 					case CMD_INSERT:
 					case CMD_DELETE:
-					case CMD_MERGE:
 						lev = LOGSTMT_MOD;
 						break;
 
@@ -3411,7 +3396,6 @@ GetCommandLogLevel(Node *parsetree)
 					case CMD_UPDATE:
 					case CMD_INSERT:
 					case CMD_DELETE:
-					case CMD_MERGE:
 						lev = LOGSTMT_MOD;
 						break;
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 608f50b061..4c0256b18a 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -53,34 +53,23 @@ typedef enum LockTupleMode
  * When heap_update, heap_delete, or heap_lock_tuple fail because the target
  * tuple is already outdated, they fill in this struct to provide information
  * to the caller about what happened.
- *
- * result is the result of HeapTupleSatisfiesUpdate, leading to the failure.
- * It's set to HeapTupleMayBeUpdated when there is no failure.
- *
  * ctid is the target's ctid link: it is the same as the target's TID if the
  * target was deleted, or the location of the replacement tuple if the target
  * was updated.
- *
  * xmax is the outdating transaction's XID.  If the caller wants to visit the
  * replacement tuple, it must check that this matches before believing the
  * replacement is really a match.
- *
  * cmax is the outdating command's CID, but only when the failure code is
  * HeapTupleSelfUpdated (i.e., something in the current transaction outdated
  * the tuple); otherwise cmax is zero.  (We make this restriction because
  * HeapTupleHeaderGetCmax doesn't work for tuples outdated in other
  * transactions.)
- *
- * lockmode is only relevant for callers of heap_update() and is the mode which
- * the caller should use in case it needs to lock the updated tuple.
  */
 typedef struct HeapUpdateFailureData
 {
-	HTSU_Result result;
 	ItemPointerData ctid;
 	TransactionId xmax;
 	CommandId	cmax;
-	LockTupleMode	lockmode;
 } HeapUpdateFailureData;
 
 
@@ -173,7 +162,7 @@ extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
 extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
 			HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd);
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
 extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_update,
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 1b79a80310..a5b8610fa2 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -206,8 +206,7 @@ extern bool ExecBRDeleteTriggers(EState *estate,
 					 EPQState *epqstate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
-					 HeapUpdateFailureData *hufdp);
+					 HeapTuple fdw_trigtuple);
 extern void ExecARDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
@@ -226,8 +225,7 @@ extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 TupleTableSlot *slot,
-					 HeapUpdateFailureData *hufdp);
+					 TupleTableSlot *slot);
 extern void ExecARUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index 9f55f6409e..03a599ad57 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -114,7 +114,6 @@ extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
 				  PartitionDispatch *pd,
 				  TupleTableSlot *slot,
 				  EState *estate);
-extern int ExecFindPartitionByOid(PartitionTupleRouting *proute, Oid partoid);
 extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
 					ResultRelInfo *resultRelInfo,
 					PartitionTupleRouting *proute,
diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h
index 28eb0093d4..b72f91898a 100644
--- a/src/include/executor/instrument.h
+++ b/src/include/executor/instrument.h
@@ -58,11 +58,8 @@ typedef struct Instrumentation
 	double		total;			/* Total total time (in seconds) */
 	double		ntuples;		/* Total tuples produced */
 	double		nloops;			/* # of run cycles for this node */
-	double		nfiltered1;		/* # tuples removed by scanqual or joinqual OR
-								 * # tuples inserted by MERGE */
-	double		nfiltered2;		/* # tuples removed by "other" quals OR
-								 * # tuples updated by MERGE */
-	double		nfiltered3;		/* # tuples deleted by MERGE */
+	double		nfiltered1;		/* # tuples removed by scanqual or joinqual */
+	double		nfiltered2;		/* # tuples removed by "other" quals */
 	BufferUsage bufusage;		/* Total buffer usage */
 } Instrumentation;
 
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 686cfa6171..0d7e579e1c 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -18,26 +18,5 @@
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
-extern TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
-						EState *estate,
-						struct PartitionTupleRouting *proute,
-						ResultRelInfo *targetRelInfo,
-						TupleTableSlot *slot);
-extern TupleTableSlot *ExecDelete(ModifyTableState *mtstate,
-		   ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *planSlot,
-		   EPQState *epqstate, EState *estate, bool *tupleDeleted,
-		   bool processReturning, HeapUpdateFailureData *hufdp,
-		   MergeActionState *actionState, bool canSetTag);
-extern TupleTableSlot *ExecUpdate(ModifyTableState *mtstate,
-		   ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
-		   TupleTableSlot *planSlot, EPQState *epqstate, EState *estate,
-		   bool *tuple_updated, HeapUpdateFailureData *hufdp,
-		   MergeActionState *actionState, bool canSetTag);
-extern TupleTableSlot *ExecInsert(ModifyTableState *mtstate,
-		   TupleTableSlot *slot,
-		   TupleTableSlot *planSlot,
-		   EState *estate,
-		   MergeActionState *actionState,
-		   bool canSetTag);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 78410b9f77..e5bdaecc4e 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -64,7 +64,6 @@ typedef struct _SPI_plan *SPIPlanPtr;
 #define SPI_OK_REL_REGISTER		15
 #define SPI_OK_REL_UNREGISTER	16
 #define SPI_OK_TD_REGISTER		17
-#define SPI_OK_MERGE			18
 
 #define SPI_OPT_NONATOMIC		(1 << 0)
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff63d179b2..6070a42b6f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -360,17 +360,8 @@ typedef struct JunkFilter
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
 	AttrNumber	jf_junkAttNo;
-	AttrNumber	jf_otherJunkAttNo;
 } JunkFilter;
 
-typedef struct MergeState
-{
-	/* List of MERGE MATCHED action states */
-	List		   *matchedActionStates;
-	/* List of MERGE NOT MATCHED action states */
-	List		   *notMatchedActionStates;
-} MergeState;
-
 /*
  * OnConflictSetState
  *
@@ -461,38 +452,8 @@ typedef struct ResultRelInfo
 
 	/* relation descriptor for root partitioned table */
 	Relation	ri_PartitionRoot;
-
-	int			ri_PartitionLeafIndex;
-	/* for running MERGE on this result relation */
-	MergeState *ri_mergeState;
-
-	/*
-	 * While executing MERGE, the target relation is processed twice; once
-	 * as a target relation and once to run a join between the target and the
-	 * source. We generate two different RTEs for these two purposes, one with
-	 * rte->inh set to false and other with rte->inh set to true.
-	 *
-	 * Since the plan re-evaluated by EvalPlanQual uses the join RTE, we must
-	 * install the updated tuple in the scan corresponding to that RTE. The
-	 * following member tracks the index of the second RTE for EvalPlanQual
-	 * purposes. ri_mergeTargetRTI is non-zero only when MERGE is in-progress.
-	 * We use ri_mergeTargetRTI to run EvalPlanQual for MERGE and
-	 * ri_RangeTableIndex elsewhere.
-	 */
-	Index		ri_mergeTargetRTI;
 } ResultRelInfo;
 
-/*
- * Get the Range table index for EvalPlanQual.
- *
- * We use the ri_mergeTargetRTI if set, otherwise use ri_RangeTableIndex.
- * ri_mergeTargetRTI should really be ever set iff we're running MERGE.
- */
-#define GetEPQRangeTableIndex(r) \
-	(((r)->ri_mergeTargetRTI > 0)  \
-	 ? (r)->ri_mergeTargetRTI \
-	 : (r)->ri_RangeTableIndex)
-
 /* ----------------
  *	  EState information
  *
@@ -1005,11 +966,6 @@ typedef struct PlanState
 		if (((PlanState *)(node))->instrument) \
 			((PlanState *)(node))->instrument->nfiltered2 += (delta); \
 	} while(0)
-#define InstrCountFiltered3(node, delta) \
-	do { \
-		if (((PlanState *)(node))->instrument) \
-			((PlanState *)(node))->instrument->nfiltered3 += (delta); \
-	} while(0)
 
 /*
  * EPQState is state for executing an EvalPlanQual recheck on a candidate
@@ -1056,20 +1012,6 @@ typedef struct ProjectSetState
 	MemoryContext argcontext;	/* context for SRF arguments */
 } ProjectSetState;
 
-/* ----------------
- *	 MergeActionState information
- * ----------------
- */
-typedef struct MergeActionState
-{
-	NodeTag		type;
-	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
-	ExprState  *whenqual;		/* WHEN AND conditions */
-	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
-	ProjectionInfo *proj;		/* tuple projection info */
-	TupleDesc	tupDesc;		/* tuple descriptor for projection */
-} MergeActionState;
-
 /* ----------------
  *	 ModifyTableState information
  * ----------------
@@ -1077,7 +1019,7 @@ typedef struct MergeActionState
 typedef struct ModifyTableState
 {
 	PlanState	ps;				/* its first field is NodeTag */
-	CmdType		operation;		/* INSERT, UPDATE, DELETE or MERGE */
+	CmdType		operation;		/* INSERT, UPDATE, or DELETE */
 	bool		canSetTag;		/* do we set the command tag/es_processed? */
 	bool		mt_done;		/* are we done? */
 	PlanState **mt_plans;		/* subplans (one per target rel) */
@@ -1093,8 +1035,6 @@ typedef struct ModifyTableState
 	List	   *mt_excludedtlist;	/* the excluded pseudo relation's tlist  */
 	TupleTableSlot *mt_conflproj;	/* CONFLICT ... SET ... projection target */
 
-	TupleTableSlot *mt_mergeproj;	/* MERGE action projection target */
-
 	/* Tuple-routing support info */
 	struct PartitionTupleRouting *mt_partition_tuple_routing;
 
@@ -1106,9 +1046,6 @@ typedef struct ModifyTableState
 
 	/* Per plan map for tuple conversion from child to root */
 	TupleConversionMap **mt_per_subplan_tupconv_maps;
-
-	/* Flags showing which subcommands are present INS/UPD/DEL/DO NOTHING */
-	int			mt_merge_subcommands;
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fce48026b6..443de22704 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -97,7 +97,6 @@ typedef enum NodeTag
 	T_PlanState,
 	T_ResultState,
 	T_ProjectSetState,
-	T_MergeActionState,
 	T_ModifyTableState,
 	T_AppendState,
 	T_MergeAppendState,
@@ -309,8 +308,6 @@ typedef enum NodeTag
 	T_InsertStmt,
 	T_DeleteStmt,
 	T_UpdateStmt,
-	T_MergeStmt,
-	T_MergeAction,
 	T_SelectStmt,
 	T_AlterTableStmt,
 	T_AlterTableCmd,
@@ -660,8 +657,7 @@ typedef enum CmdType
 	CMD_SELECT,					/* select stmt */
 	CMD_UPDATE,					/* update stmt */
 	CMD_INSERT,					/* insert stmt */
-	CMD_DELETE,					/* delete stmt */
-	CMD_MERGE,					/* merge stmt */
+	CMD_DELETE,
 	CMD_UTILITY,				/* cmds like create, destroy, copy, vacuum,
 								 * etc. */
 	CMD_NOTHING					/* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d005beeba8..92082b3a7a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -38,7 +38,7 @@ typedef enum OverridingKind
 typedef enum QuerySource
 {
 	QSRC_ORIGINAL,				/* original parsetree (explicit query) */
-	QSRC_PARSER,				/* added by parse analysis in MERGE */
+	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
 	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
@@ -107,7 +107,7 @@ typedef struct Query
 {
 	NodeTag		type;
 
-	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
+	CmdType		commandType;	/* select|insert|update|delete|utility */
 
 	QuerySource querySource;	/* where did I come from? */
 
@@ -118,7 +118,7 @@ typedef struct Query
 	Node	   *utilityStmt;	/* non-null if commandType == CMD_UTILITY */
 
 	int			resultRelation; /* rtable index of target relation for
-								 * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
+								 * INSERT/UPDATE/DELETE; 0 for SELECT */
 
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
@@ -169,9 +169,6 @@ typedef struct Query
 	List	   *withCheckOptions;	/* a list of WithCheckOption's, which are
 									 * only added during rewrite and therefore
 									 * are not written out as part of Query. */
-	int			mergeTarget_relation;
-	List	   *mergeSourceTargetList;
-	List	   *mergeActionList;	/* list of actions for MERGE (only) */
 
 	/*
 	 * The following two fields identify the portion of the source text string
@@ -1131,9 +1128,7 @@ typedef enum WCOKind
 	WCO_VIEW_CHECK,				/* WCO on an auto-updatable view */
 	WCO_RLS_INSERT_CHECK,		/* RLS INSERT WITH CHECK policy */
 	WCO_RLS_UPDATE_CHECK,		/* RLS UPDATE WITH CHECK policy */
-	WCO_RLS_CONFLICT_CHECK,		/* RLS ON CONFLICT DO UPDATE USING policy */
-	WCO_RLS_MERGE_UPDATE_CHECK, /* RLS MERGE UPDATE USING policy */
-	WCO_RLS_MERGE_DELETE_CHECK	/* RLS MERGE DELETE USING policy */
+	WCO_RLS_CONFLICT_CHECK		/* RLS ON CONFLICT DO UPDATE USING policy */
 } WCOKind;
 
 typedef struct WithCheckOption
@@ -1508,30 +1503,6 @@ typedef struct UpdateStmt
 	WithClause *withClause;		/* WITH clause */
 } UpdateStmt;
 
-/* ----------------------
- *		Merge Statement
- * ----------------------
- */
-typedef struct MergeStmt
-{
-	NodeTag		type;
-	RangeVar   *relation;			/* target relation to merge into */
-	Node	   *source_relation;	/* source relation */
-	Node	   *join_condition; /* join condition between source and target */
-	List	   *mergeActionList;	/* list of MergeAction(s) */
-} MergeStmt;
-
-typedef struct MergeAction
-{
-	NodeTag		type;
-	bool		matched;		/* true=MATCHED, false=NOT MATCHED */
-	Node	   *condition;		/* WHEN AND conditions (raw parser) */
-	Node	   *qual;			/* transformed WHEN AND conditions */
-	CmdType		commandType;	/* INSERT/UPDATE/DELETE/DO NOTHING */
-	Node	   *stmt;			/* T_UpdateStmt etc */
-	List	   *targetList;		/* the target list (of ResTarget) */
-} MergeAction;
-
 /* ----------------------
  *		Select Statement
  *
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0a797f0a05..c922216b7d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -18,7 +18,6 @@
 #include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
-#include "nodes/parsenodes.h"
 #include "nodes/primnodes.h"
 
 
@@ -43,7 +42,7 @@ typedef struct PlannedStmt
 {
 	NodeTag		type;
 
-	CmdType		commandType;	/* select|insert|update|delete|merge|utility */
+	CmdType		commandType;	/* select|insert|update|delete|utility */
 
 	uint64		queryId;		/* query identifier (copied from Query) */
 
@@ -217,14 +216,13 @@ typedef struct ProjectSet
 typedef struct ModifyTable
 {
 	Plan		plan;
-	CmdType		operation;		/* INSERT, UPDATE, DELETE or MERGE */
+	CmdType		operation;		/* INSERT, UPDATE, or DELETE */
 	bool		canSetTag;		/* do we set the command tag/es_processed? */
 	Index		nominalRelation;	/* Parent RT index for use of EXPLAIN */
 	/* RT indexes of non-leaf tables in a partition tree */
 	List	   *partitioned_rels;
 	bool		partColsUpdated;	/* some part key in hierarchy updated */
 	List	   *resultRelations;	/* integer list of RT indexes */
-	Index	    mergeTargetRelation;	/* RT index of the merge target */
 	int			resultRelIndex; /* index of first resultRel in plan's list */
 	int			rootResultRelIndex; /* index of the partitioned table root */
 	List	   *plans;			/* plan(s) producing source data */
@@ -240,8 +238,6 @@ typedef struct ModifyTable
 	Node	   *onConflictWhere;	/* WHERE for ON CONFLICT UPDATE */
 	Index		exclRelRTI;		/* RTI of the EXCLUDED pseudo relation */
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
-	List	   *mergeSourceTargetList;
-	List	   *mergeActionList;	/* actions for MERGE */
 } ModifyTable;
 
 /* ----------------
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index a2dde70de5..ea5251c6be 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1670,7 +1670,7 @@ typedef struct LockRowsPath
 } LockRowsPath;
 
 /*
- * ModifyTablePath represents performing INSERT/UPDATE/DELETE/MERGE
+ * ModifyTablePath represents performing INSERT/UPDATE/DELETE modifications
  *
  * We represent most things that will be in the ModifyTable plan node
  * literally, except we have child Path(s) not Plan(s).  But analysis of the
@@ -1679,14 +1679,13 @@ typedef struct LockRowsPath
 typedef struct ModifyTablePath
 {
 	Path		path;
-	CmdType		operation;		/* INSERT, UPDATE, DELETE or MERGE */
+	CmdType		operation;		/* INSERT, UPDATE, or DELETE */
 	bool		canSetTag;		/* do we set the command tag/es_processed? */
 	Index		nominalRelation;	/* Parent RT index for use of EXPLAIN */
 	/* RT indexes of non-leaf tables in a partition tree */
 	List	   *partitioned_rels;
 	bool		partColsUpdated;	/* some part key in hierarchy updated */
 	List	   *resultRelations;	/* integer list of RT indexes */
-	Index	  	mergeTargetRelation;/* RT index of merge target relation */
 	List	   *subpaths;		/* Path(s) producing source data */
 	List	   *subroots;		/* per-target-table PlannerInfos */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
@@ -1694,8 +1693,6 @@ typedef struct ModifyTablePath
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	OnConflictExpr *onconflict; /* ON CONFLICT clause, or NULL */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
-	List	   *mergeSourceTargetList;
-	List	   *mergeActionList;	/* actions for MERGE */
 } ModifyTablePath;
 
 /*
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 895bf6959d..381bc30813 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -241,14 +241,11 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 						CmdType operation, bool canSetTag,
 						Index nominalRelation, List *partitioned_rels,
 						bool partColsUpdated,
-						List *resultRelations,
-						Index	mergeTargetRelation,
-						List *subpaths,
+						List *resultRelations, List *subpaths,
 						List *subroots,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
-						List *mergeSourceTargetList,
-						List *mergeActionList, int epqParam);
+						int epqParam);
 extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
 				  Path *subpath,
 				  Node *limitOffset, Node *limitCount,
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index 41fb10666e..687ae1b5b7 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -32,11 +32,6 @@ extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
 				  bool locked_from_parent,
 				  bool resolve_unknowns);
 
-extern List *transformInsertRow(ParseState *pstate, List *exprlist,
-				   List *stmtcols, List *icolumns, List *attrnos,
-				   bool strip_indirection);
-extern List *transformUpdateTargetList(ParseState *pstate,
-						  List *targetList);
 extern Query *transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree);
 extern Query *transformStmt(ParseState *pstate, Node *parseTree);
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 4dff55a8e9..cf32197bc3 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -244,10 +244,8 @@ PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
-PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
 PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)
 PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
-PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD)
 PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index 30121c98ed..2c0e092862 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -20,10 +20,7 @@ extern void transformFromClause(ParseState *pstate, List *frmList);
 extern int setTargetTable(ParseState *pstate, RangeVar *relation,
 			   bool inh, bool alsoSource, AclMode requiredPerms);
 extern bool interpretOidsOption(List *defList, bool allowOids);
-extern Node *transformFromClauseItem(ParseState *pstate, Node *n,
-						RangeTblEntry **top_rte, int *top_rti,
-						RangeTblEntry **right_rte, int *right_rti,
-						List **namespace);
+
 extern Node *transformWhereClause(ParseState *pstate, Node *clause,
 					 ParseExprKind exprKind, const char *constructName);
 extern Node *transformLimitClause(ParseState *pstate, Node *clause,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 3fd2151ccb..0230543810 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -50,7 +50,6 @@ typedef enum ParseExprKind
 	EXPR_KIND_INSERT_TARGET,	/* INSERT target list item */
 	EXPR_KIND_UPDATE_SOURCE,	/* UPDATE assignment source item */
 	EXPR_KIND_UPDATE_TARGET,	/* UPDATE assignment target item */
-	EXPR_KIND_MERGE_WHEN_AND,	/* MERGE WHEN ... AND condition */
 	EXPR_KIND_GROUP_BY,			/* GROUP BY */
 	EXPR_KIND_ORDER_BY,			/* ORDER BY */
 	EXPR_KIND_DISTINCT_ON,		/* DISTINCT ON */
@@ -128,7 +127,7 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * p_parent_cte: CommonTableExpr that immediately contains the current query,
  * if any.
  *
- * p_target_relation: target relation, if query is INSERT/UPDATE/DELETE/MERGE
+ * p_target_relation: target relation, if query is INSERT, UPDATE, or DELETE.
  *
  * p_target_rangetblentry: target relation's entry in the rtable list.
  *
@@ -182,7 +181,7 @@ struct ParseState
 	List	   *p_ctenamespace; /* current namespace for common table exprs */
 	List	   *p_future_ctes;	/* common table exprs not yet in namespace */
 	CommonTableExpr *p_parent_cte;	/* this query's containing CTE */
-	Relation	p_target_relation;	/* INSERT/UPDATE/DELETE/MERGE target rel */
+	Relation	p_target_relation;	/* INSERT/UPDATE/DELETE target rel */
 	RangeTblEntry *p_target_rangetblentry;	/* target rel's RTE */
 	bool		p_is_insert;	/* process assignment like INSERT not UPDATE */
 	List	   *p_windowdefs;	/* raw representations of window clauses */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 1ab5de3942..8128199fc3 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,7 +25,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation);
-extern void rewriteTargetListMerge(Query *parsetree, Relation target_relation);
 
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index a432636322..4c0114c514 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -3055,9 +3055,9 @@ PQoidValue(const PGresult *res)
 
 /*
  * PQcmdTuples -
- *	If the last command was INSERT/UPDATE/DELETE/MERGE/MOVE/FETCH/COPY,
- *	return a string containing the number of inserted/affected tuples.
- *	If not, return "".
+ *	If the last command was INSERT/UPDATE/DELETE/MOVE/FETCH/COPY, return
+ *	a string containing the number of inserted/affected tuples. If not,
+ *	return "".
  *
  *	XXX: this should probably return an int
  */
@@ -3084,8 +3084,7 @@ PQcmdTuples(PGresult *res)
 			 strncmp(res->cmdStatus, "DELETE ", 7) == 0 ||
 			 strncmp(res->cmdStatus, "UPDATE ", 7) == 0)
 		p = res->cmdStatus + 7;
-	else if (strncmp(res->cmdStatus, "FETCH ", 6) == 0 ||
-			 strncmp(res->cmdStatus, "MERGE ", 6) == 0)
+	else if (strncmp(res->cmdStatus, "FETCH ", 6) == 0)
 		p = res->cmdStatus + 6;
 	else if (strncmp(res->cmdStatus, "MOVE ", 5) == 0 ||
 			 strncmp(res->cmdStatus, "COPY ", 5) == 0)
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 67123f85c9..f574aa77f0 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3967,7 +3967,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 
 	/*
 	 * On the first call for this statement generate the plan, and detect
-	 * whether the statement is INSERT/UPDATE/DELETE/MERGE
+	 * whether the statement is INSERT/UPDATE/DELETE
 	 */
 	if (expr->plan == NULL)
 	{
@@ -3988,7 +3988,6 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 				{
 					if (q->commandType == CMD_INSERT ||
 						q->commandType == CMD_UPDATE ||
-						q->commandType == CMD_MERGE ||
 						q->commandType == CMD_DELETE)
 						stmt->mod_stmt = true;
 				}
@@ -4046,7 +4045,6 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 		case SPI_OK_INSERT_RETURNING:
 		case SPI_OK_UPDATE_RETURNING:
 		case SPI_OK_DELETE_RETURNING:
-		case SPI_OK_MERGE:
 			Assert(stmt->mod_stmt);
 			exec_set_found(estate, (SPI_processed != 0));
 			break;
@@ -4224,7 +4222,6 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
 		case SPI_OK_INSERT_RETURNING:
 		case SPI_OK_UPDATE_RETURNING:
 		case SPI_OK_DELETE_RETURNING:
-		case SPI_OK_MERGE:
 		case SPI_OK_UTILITY:
 		case SPI_OK_REWRITTEN:
 			break;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index d928efa13e..b8562ca8b4 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -304,7 +304,6 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_LAST
 %token <keyword>	K_LOG
 %token <keyword>	K_LOOP
-%token <keyword>	K_MERGE
 %token <keyword>	K_MESSAGE
 %token <keyword>	K_MESSAGE_TEXT
 %token <keyword>	K_MOVE
@@ -1948,10 +1947,6 @@ stmt_execsql	: K_IMPORT
 					{
 						$$ = make_execsql_stmt(K_INSERT, @1);
 					}
-				| K_MERGE
-					{
-						$$ = make_execsql_stmt(K_MERGE, @1);
-					}
 				| T_WORD
 					{
 						int			tok;
@@ -2474,7 +2469,6 @@ unreserved_keyword	:
 				| K_IS
 				| K_LAST
 				| K_LOG
-				| K_MERGE
 				| K_MESSAGE
 				| K_MESSAGE_TEXT
 				| K_MOVE
@@ -2936,8 +2930,6 @@ make_execsql_stmt(int firsttoken, int location)
 		{
 			if (prev_tok == K_INSERT)
 				continue;		/* INSERT INTO is not an INTO-target */
-			if (prev_tok == K_MERGE)
-				continue;		/* MERGE INTO is not an INTO-target */
 			if (firsttoken == K_IMPORT)
 				continue;		/* IMPORT ... INTO is not an INTO-target */
 			if (have_into)
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index 81b5bd1fe0..08614a89a8 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -138,7 +138,6 @@ static const ScanKeyword unreserved_keywords[] = {
 	PG_KEYWORD("is", K_IS, UNRESERVED_KEYWORD)
 	PG_KEYWORD("last", K_LAST, UNRESERVED_KEYWORD)
 	PG_KEYWORD("log", K_LOG, UNRESERVED_KEYWORD)
-	PG_KEYWORD("merge", K_MERGE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("message", K_MESSAGE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("message_text", K_MESSAGE_TEXT, UNRESERVED_KEYWORD)
 	PG_KEYWORD("move", K_MOVE, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 3f617201d6..dc90fe532f 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -846,8 +846,8 @@ typedef struct PLpgSQL_stmt_execsql
 	PLpgSQL_stmt_type cmd_type;
 	int			lineno;
 	PLpgSQL_expr *sqlstmt;
-	bool		mod_stmt;		/* is the stmt INSERT/UPDATE/DELETE/MERGE?
-								 * Note mod_stmt is set when we plan the query */
+	bool		mod_stmt;		/* is the stmt INSERT/UPDATE/DELETE?  Note:
+								 * mod_stmt is set when we plan the query */
 	bool		into;			/* INTO supplied? */
 	bool		strict;			/* INTO STRICT flag */
 	PLpgSQL_variable *target;	/* INTO target (record or row) */
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 99dd7c6bdb..d3965fe73f 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -33,10 +33,6 @@ test: insert-conflict-do-update
 test: insert-conflict-do-update-2
 test: insert-conflict-do-update-3
 test: insert-conflict-toast
-test: merge-insert-update
-test: merge-delete
-test: merge-update
-test: merge-match-recheck
 test: delete-abort-savept
 test: delete-abort-savept-2
 test: aborted-keyrevoke
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 3a6016c80a..d7d5178f5d 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -386,58 +386,3 @@ CREATE TABLE itest_child PARTITION OF itest_parent (
 ) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
 ERROR:  identity columns are not supported on partitions
 DROP TABLE itest_parent;
--- MERGE tests
-CREATE TABLE itest14 (a int GENERATED ALWAYS AS IDENTITY, b text);
-CREATE TABLE itest15 (a int GENERATED BY DEFAULT AS IDENTITY, b text);
-MERGE INTO itest14 t
-USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) VALUES (s.s_a, s.s_b);
-ERROR:  cannot insert into column "a"
-DETAIL:  Column "a" is an identity column defined as GENERATED ALWAYS.
-HINT:  Use OVERRIDING SYSTEM VALUE to override.
-MERGE INTO itest14 t
-USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
-ERROR:  cannot insert into column "a"
-DETAIL:  Column "a" is an identity column defined as GENERATED ALWAYS.
-HINT:  Use OVERRIDING SYSTEM VALUE to override.
-MERGE INTO itest14 t
-USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
-MERGE INTO itest15 t
-USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) VALUES (s.s_a, s.s_b);
-MERGE INTO itest15 t
-USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
-MERGE INTO itest15 t
-USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
-SELECT * FROM itest14;
- a  |         b         
-----+-------------------
- 30 | inserted by merge
-(1 row)
-
-SELECT * FROM itest15;
- a  |         b         
-----+-------------------
- 10 | inserted by merge
-  1 | inserted by merge
- 30 | inserted by merge
-(3 rows)
-
-DROP TABLE itest14;
-DROP TABLE itest15;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 864f2c1345..ac8968d24f 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -517,104 +517,6 @@ SELECT atest6 FROM atest6; -- ok
 (0 rows)
 
 COPY atest6 TO stdout; -- ok
--- test column privileges with MERGE
-SET SESSION AUTHORIZATION regress_priv_user1;
-CREATE TABLE mtarget (a int, b text);
-CREATE TABLE msource (a int, b text);
-INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2');
-INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3');
-GRANT SELECT (a) ON msource TO regress_priv_user4;
-GRANT SELECT (a) ON mtarget TO regress_priv_user4;
-GRANT INSERT (a,b) ON mtarget TO regress_priv_user4;
-GRANT UPDATE (b) ON mtarget TO regress_priv_user4;
-SET SESSION AUTHORIZATION regress_priv_user4;
---
--- test source privileges
---
--- fail (no SELECT priv on s.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, NULL);
-ERROR:  permission denied for table msource
--- fail (s.b used in the INSERTed values)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = 'x'
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, b);
-ERROR:  permission denied for table msource
--- fail (s.b used in the WHEN quals)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND s.b = 'x' THEN
-	UPDATE SET b = 'x'
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, NULL);
-ERROR:  permission denied for table msource
--- this should be ok since only s.a is accessed
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = 'ok'
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, NULL);
-ROLLBACK;
-SET SESSION AUTHORIZATION regress_priv_user1;
-GRANT SELECT (b) ON msource TO regress_priv_user4;
-SET SESSION AUTHORIZATION regress_priv_user4;
--- should now be ok
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, b);
-ROLLBACK;
---
--- test target privileges
---
--- fail (no SELECT priv on t.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = t.b
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, NULL);
-ERROR:  permission denied for table mtarget
--- fail (no UPDATE on t.a)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = s.b, a = t.a + 1
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, b);
-ERROR:  permission denied for table mtarget
--- fail (no SELECT on t.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
-	UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, b);
-ERROR:  permission denied for table mtarget
--- ok
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = s.b;
-ROLLBACK;
--- fail (no DELETE)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
-	DELETE;
-ERROR:  permission denied for table mtarget
--- grant delete privileges
-SET SESSION AUTHORIZATION regress_priv_user1;
-GRANT DELETE ON mtarget TO regress_priv_user4;
--- should be ok now
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
-	DELETE;
-ROLLBACK;
 -- check error reporting with column privs
 SET SESSION AUTHORIZATION regress_priv_user1;
 CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index bf7af3ba82..f1ae40df61 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -2138,188 +2138,6 @@ ERROR:  new row violates row-level security policy (USING expression) for table
 INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
     ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';
 ERROR:  new row violates row-level security policy for table "document"
---
--- MERGE
---
-RESET SESSION AUTHORIZATION;
-DROP POLICY p3_with_all ON document;
-ALTER TABLE document ADD COLUMN dnotes text DEFAULT '';
--- all documents are readable
-CREATE POLICY p1 ON document FOR SELECT USING (true);
--- one may insert documents only authored by them
-CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
--- one may only update documents in 'novel' category
-CREATE POLICY p3 ON document FOR UPDATE
-  USING (cid = (SELECT cid from category WHERE cname = 'novel'))
-  WITH CHECK (dauthor = current_user);
--- one may only delete documents in 'manga' category
-CREATE POLICY p4 ON document FOR DELETE
-  USING (cid = (SELECT cid from category WHERE cname = 'manga'));
-SELECT * FROM document;
- did | cid | dlevel |      dauthor      |              dtitle              | dnotes 
------+-----+--------+-------------------+----------------------------------+--------
-   1 |  11 |      1 | regress_rls_bob   | my first novel                   | 
-   3 |  22 |      2 | regress_rls_bob   | my science fiction               | 
-   4 |  44 |      1 | regress_rls_bob   | my first manga                   | 
-   5 |  44 |      2 | regress_rls_bob   | my second manga                  | 
-   6 |  22 |      1 | regress_rls_carol | great science fiction            | 
-   7 |  33 |      2 | regress_rls_carol | great technology book            | 
-   8 |  44 |      1 | regress_rls_carol | great manga                      | 
-   9 |  22 |      1 | regress_rls_dave  | awesome science fiction          | 
-  10 |  33 |      2 | regress_rls_dave  | awesome technology book          | 
-  11 |  33 |      1 | regress_rls_carol | hoge                             | 
-  33 |  22 |      1 | regress_rls_bob   | okay science fiction             | 
-   2 |  11 |      2 | regress_rls_bob   | my first novel                   | 
-  78 |  33 |      1 | regress_rls_bob   | some technology novel            | 
-  79 |  33 |      1 | regress_rls_bob   | technology book, can only insert | 
-(14 rows)
-
-SET SESSION AUTHORIZATION regress_rls_bob;
--- Fails, since update violates WITH CHECK qual on dauthor
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice';
-ERROR:  new row violates row-level security policy for table "document"
--- Should be OK since USING and WITH CHECK quals pass
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge2 ';
--- Even when dauthor is updated explicitly, but to the existing value
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dauthor = 'regress_rls_bob';
--- There is a MATCH for did = 3, but UPDATE's USING qual does not allow
--- updating an item in category 'science fiction'
-MERGE INTO document d
-USING (SELECT 3 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge ';
-ERROR:  target row violates row-level security policy (USING expression) for table "document"
--- The same thing with DELETE action, but fails again because no permissions
--- to delete items in 'science fiction' category that did 3 belongs to.
-MERGE INTO document d
-USING (SELECT 3 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	DELETE;
-ERROR:  target row violates row-level security policy (USING expression) for table "document"
--- Document with did 4 belongs to 'manga' category which is allowed for
--- deletion. But this fails because the UPDATE action is matched first and
--- UPDATE policy does not allow updation in the category.
-MERGE INTO document d
-USING (SELECT 4 as sdid) s
-ON did = s.sdid
-WHEN MATCHED AND dnotes = '' THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge '
-WHEN MATCHED THEN
-	DELETE;
-ERROR:  target row violates row-level security policy (USING expression) for table "document"
--- UPDATE action is not matched this time because of the WHEN AND qual.
--- DELETE still fails because role regress_rls_bob does not have SELECT
--- privileges on 'manga' category row in the category table.
-MERGE INTO document d
-USING (SELECT 4 as sdid) s
-ON did = s.sdid
-WHEN MATCHED AND dnotes <> '' THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge '
-WHEN MATCHED THEN
-	DELETE;
-ERROR:  target row violates row-level security policy (USING expression) for table "document"
-SELECT * FROM document WHERE did = 4;
- did | cid | dlevel |     dauthor     |     dtitle     | dnotes 
------+-----+--------+-----------------+----------------+--------
-   4 |  44 |      1 | regress_rls_bob | my first manga | 
-(1 row)
-
--- Switch to regress_rls_carol role and try the DELETE again. It should succeed
--- this time
-RESET SESSION AUTHORIZATION;
-SET SESSION AUTHORIZATION regress_rls_carol;
-MERGE INTO document d
-USING (SELECT 4 as sdid) s
-ON did = s.sdid
-WHEN MATCHED AND dnotes <> '' THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge '
-WHEN MATCHED THEN
-	DELETE;
--- Switch back to regress_rls_bob role
-RESET SESSION AUTHORIZATION;
-SET SESSION AUTHORIZATION regress_rls_bob;
--- Try INSERT action. This fails because we are trying to insert
--- dauthor = regress_rls_dave and INSERT's WITH CHECK does not allow
--- that
-MERGE INTO document d
-USING (SELECT 12 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	DELETE
-WHEN NOT MATCHED THEN
-	INSERT VALUES (12, 11, 1, 'regress_rls_dave', 'another novel');
-ERROR:  new row violates row-level security policy for table "document"
--- This should be fine
-MERGE INTO document d
-USING (SELECT 12 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	DELETE
-WHEN NOT MATCHED THEN
-	INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
--- ok
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge4 '
-WHEN NOT MATCHED THEN
-	INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
--- drop and create a new SELECT policy which prevents us from reading
--- any document except with category 'magna'
-RESET SESSION AUTHORIZATION;
-DROP POLICY p1 ON document;
-CREATE POLICY p1 ON document FOR SELECT
-  USING (cid = (SELECT cid from category WHERE cname = 'manga'));
-SET SESSION AUTHORIZATION regress_rls_bob;
--- MERGE can no longer see the matching row and hence attempts the
--- NOT MATCHED action, which results in unique key violation
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge5 '
-WHEN NOT MATCHED THEN
-	INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
-ERROR:  duplicate key value violates unique constraint "document_pkey"
-RESET SESSION AUTHORIZATION;
--- drop the restrictive SELECT policy so that we can look at the
--- final state of the table
-DROP POLICY p1 ON document;
--- Just check everything went per plan
-SELECT * FROM document;
- did | cid | dlevel |      dauthor      |              dtitle              |                                dnotes                                 
------+-----+--------+-------------------+----------------------------------+-----------------------------------------------------------------------
-   3 |  22 |      2 | regress_rls_bob   | my science fiction               | 
-   5 |  44 |      2 | regress_rls_bob   | my second manga                  | 
-   6 |  22 |      1 | regress_rls_carol | great science fiction            | 
-   7 |  33 |      2 | regress_rls_carol | great technology book            | 
-   8 |  44 |      1 | regress_rls_carol | great manga                      | 
-   9 |  22 |      1 | regress_rls_dave  | awesome science fiction          | 
-  10 |  33 |      2 | regress_rls_dave  | awesome technology book          | 
-  11 |  33 |      1 | regress_rls_carol | hoge                             | 
-  33 |  22 |      1 | regress_rls_bob   | okay science fiction             | 
-   2 |  11 |      2 | regress_rls_bob   | my first novel                   | 
-  78 |  33 |      1 | regress_rls_bob   | some technology novel            | 
-  79 |  33 |      1 | regress_rls_bob   | technology book, can only insert | 
-  12 |  11 |      1 | regress_rls_bob   | another novel                    | 
-   1 |  11 |      1 | regress_rls_bob   | my first novel                   |  notes added by merge2  notes added by merge3  notes added by merge4 
-(14 rows)
-
 --
 -- ROLE/GROUP
 --
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index a4ec5690aa..ae0cd253d5 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3265,37 +3265,6 @@ CREATE RULE rules_parted_table_insert AS ON INSERT to rules_parted_table
 ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
 DROP TABLE rules_parted_table;
 --
--- test MERGE
---
-CREATE TABLE rule_merge1 (a int, b text);
-CREATE TABLE rule_merge2 (a int, b text);
-CREATE RULE rule1 AS ON INSERT TO rule_merge1
-	DO INSTEAD INSERT INTO rule_merge2 VALUES (NEW.*);
-CREATE RULE rule2 AS ON UPDATE TO rule_merge1
-	DO INSTEAD UPDATE rule_merge2 SET a = NEW.a, b = NEW.b
-	WHERE a = OLD.a;
-CREATE RULE rule3 AS ON DELETE TO rule_merge1
-	DO INSTEAD DELETE FROM rule_merge2 WHERE a = OLD.a;
--- MERGE not supported for table with rules
-MERGE INTO rule_merge1 t USING (SELECT 1 AS a) s
-	ON t.a = s.a
-	WHEN MATCHED AND t.a < 2 THEN
-		UPDATE SET b = b || ' updated by merge'
-	WHEN MATCHED AND t.a > 2 THEN
-		DELETE
-	WHEN NOT MATCHED THEN
-		INSERT VALUES (s.a, '');
-ERROR:  MERGE is not supported for relations with rules
--- should be ok with the other table though
-MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
-	ON t.a = s.a
-	WHEN MATCHED AND t.a < 2 THEN
-		UPDATE SET b = b || ' updated by merge'
-	WHEN MATCHED AND t.a > 2 THEN
-		DELETE
-	WHEN NOT MATCHED THEN
-		INSERT VALUES (s.a, '');
---
 -- Test enabling/disabling
 --
 CREATE TABLE ruletest1 (a int);
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 7f4a94ef7d..387e40d67d 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -2761,54 +2761,6 @@ delete from self_ref where a = 1;
 NOTICE:  trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT
 NOTICE:  trigger = self_ref_s_trig, old table = (1,), (2,1), (3,2), (4,3)
 drop table self_ref;
---
--- test transition tables with MERGE
---
-create table merge_target_table (a int primary key, b text);
-create trigger merge_target_table_insert_trig
-  after insert on merge_target_table referencing new table as new_table
-  for each statement execute procedure dump_insert();
-create trigger merge_target_table_update_trig
-  after update on merge_target_table referencing old table as old_table new table as new_table
-  for each statement execute procedure dump_update();
-create trigger merge_target_table_delete_trig
-  after delete on merge_target_table referencing old table as old_table
-  for each statement execute procedure dump_delete();
-create table merge_source_table (a int, b text);
-insert into merge_source_table
-  values (1, 'initial1'), (2, 'initial2'),
-		 (3, 'initial3'), (4, 'initial4');
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when not matched then
-  insert values (a, b);
-NOTICE:  trigger = merge_target_table_insert_trig, new table = (1,initial1), (2,initial2), (3,initial3), (4,initial4)
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when matched and s.a <= 2 then
-	update set b = t.b || ' updated by merge'
-when matched and s.a > 2 then
-	delete
-when not matched then
-  insert values (a, b);
-NOTICE:  trigger = merge_target_table_delete_trig, old table = (3,initial3), (4,initial4)
-NOTICE:  trigger = merge_target_table_update_trig, old table = (1,initial1), (2,initial2), new table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge")
-NOTICE:  trigger = merge_target_table_insert_trig, new table = <NULL>
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when matched and s.a <= 2 then
-	update set b = t.b || ' updated again by merge'
-when matched and s.a > 2 then
-	delete
-when not matched then
-  insert values (a, b);
-NOTICE:  trigger = merge_target_table_delete_trig, old table = <NULL>
-NOTICE:  trigger = merge_target_table_update_trig, old table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge"), new table = (1,"initial1 updated by merge updated again by merge"), (2,"initial2 updated by merge updated again by merge")
-NOTICE:  trigger = merge_target_table_insert_trig, new table = (3,initial3), (4,initial4)
-drop table merge_source_table, merge_target_table;
 -- cleanup
 drop function dump_insert();
 drop function dump_update();
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 20d6745730..d858a0e7db 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index merge
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a08169f256..99f8ca37ba 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -123,7 +123,6 @@ test: tablesample
 test: groupingsets
 test: drop_operator
 test: password
-test: merge
 test: alter_generic
 test: alter_operator
 test: misc
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index f8f34eaf18..a35f331f4e 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -246,48 +246,3 @@ CREATE TABLE itest_child PARTITION OF itest_parent (
     f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY
 ) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
 DROP TABLE itest_parent;
-
--- MERGE tests
-CREATE TABLE itest14 (a int GENERATED ALWAYS AS IDENTITY, b text);
-CREATE TABLE itest15 (a int GENERATED BY DEFAULT AS IDENTITY, b text);
-
-MERGE INTO itest14 t
-USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest14 t
-USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest14 t
-USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest15 t
-USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest15 t
-USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest15 t
-USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
-	INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
-
-SELECT * FROM itest14;
-SELECT * FROM itest15;
-DROP TABLE itest14;
-DROP TABLE itest15;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 0a8abf2076..f7f3bbbeeb 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -349,114 +349,6 @@ UPDATE atest5 SET one = 1; -- fail
 SELECT atest6 FROM atest6; -- ok
 COPY atest6 TO stdout; -- ok
 
--- test column privileges with MERGE
-SET SESSION AUTHORIZATION regress_priv_user1;
-CREATE TABLE mtarget (a int, b text);
-CREATE TABLE msource (a int, b text);
-INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2');
-INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3');
-
-GRANT SELECT (a) ON msource TO regress_priv_user4;
-GRANT SELECT (a) ON mtarget TO regress_priv_user4;
-GRANT INSERT (a,b) ON mtarget TO regress_priv_user4;
-GRANT UPDATE (b) ON mtarget TO regress_priv_user4;
-
-SET SESSION AUTHORIZATION regress_priv_user4;
-
---
--- test source privileges
---
-
--- fail (no SELECT priv on s.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, NULL);
-
--- fail (s.b used in the INSERTed values)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = 'x'
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, b);
-
--- fail (s.b used in the WHEN quals)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND s.b = 'x' THEN
-	UPDATE SET b = 'x'
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, NULL);
-
--- this should be ok since only s.a is accessed
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = 'ok'
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, NULL);
-ROLLBACK;
-
-SET SESSION AUTHORIZATION regress_priv_user1;
-GRANT SELECT (b) ON msource TO regress_priv_user4;
-SET SESSION AUTHORIZATION regress_priv_user4;
-
--- should now be ok
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, b);
-ROLLBACK;
-
---
--- test target privileges
---
-
--- fail (no SELECT priv on t.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = t.b
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, NULL);
-
--- fail (no UPDATE on t.a)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = s.b, a = t.a + 1
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, b);
-
--- fail (no SELECT on t.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
-	UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
-	INSERT VALUES (a, b);
-
--- ok
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
-	UPDATE SET b = s.b;
-ROLLBACK;
-
--- fail (no DELETE)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
-	DELETE;
-
--- grant delete privileges
-SET SESSION AUTHORIZATION regress_priv_user1;
-GRANT DELETE ON mtarget TO regress_priv_user4;
--- should be ok now
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
-	DELETE;
-ROLLBACK;
-
 -- check error reporting with column privs
 SET SESSION AUTHORIZATION regress_priv_user1;
 CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index 6c75208998..f3a31dbee0 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -812,162 +812,6 @@ INSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel')
 INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
     ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';
 
---
--- MERGE
---
-RESET SESSION AUTHORIZATION;
-DROP POLICY p3_with_all ON document;
-
-ALTER TABLE document ADD COLUMN dnotes text DEFAULT '';
--- all documents are readable
-CREATE POLICY p1 ON document FOR SELECT USING (true);
--- one may insert documents only authored by them
-CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
--- one may only update documents in 'novel' category
-CREATE POLICY p3 ON document FOR UPDATE
-  USING (cid = (SELECT cid from category WHERE cname = 'novel'))
-  WITH CHECK (dauthor = current_user);
--- one may only delete documents in 'manga' category
-CREATE POLICY p4 ON document FOR DELETE
-  USING (cid = (SELECT cid from category WHERE cname = 'manga'));
-
-SELECT * FROM document;
-
-SET SESSION AUTHORIZATION regress_rls_bob;
-
--- Fails, since update violates WITH CHECK qual on dauthor
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice';
-
--- Should be OK since USING and WITH CHECK quals pass
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge2 ';
-
--- Even when dauthor is updated explicitly, but to the existing value
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dauthor = 'regress_rls_bob';
-
--- There is a MATCH for did = 3, but UPDATE's USING qual does not allow
--- updating an item in category 'science fiction'
-MERGE INTO document d
-USING (SELECT 3 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge ';
-
--- The same thing with DELETE action, but fails again because no permissions
--- to delete items in 'science fiction' category that did 3 belongs to.
-MERGE INTO document d
-USING (SELECT 3 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	DELETE;
-
--- Document with did 4 belongs to 'manga' category which is allowed for
--- deletion. But this fails because the UPDATE action is matched first and
--- UPDATE policy does not allow updation in the category.
-MERGE INTO document d
-USING (SELECT 4 as sdid) s
-ON did = s.sdid
-WHEN MATCHED AND dnotes = '' THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge '
-WHEN MATCHED THEN
-	DELETE;
-
--- UPDATE action is not matched this time because of the WHEN AND qual.
--- DELETE still fails because role regress_rls_bob does not have SELECT
--- privileges on 'manga' category row in the category table.
-MERGE INTO document d
-USING (SELECT 4 as sdid) s
-ON did = s.sdid
-WHEN MATCHED AND dnotes <> '' THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge '
-WHEN MATCHED THEN
-	DELETE;
-
-SELECT * FROM document WHERE did = 4;
-
--- Switch to regress_rls_carol role and try the DELETE again. It should succeed
--- this time
-RESET SESSION AUTHORIZATION;
-SET SESSION AUTHORIZATION regress_rls_carol;
-
-MERGE INTO document d
-USING (SELECT 4 as sdid) s
-ON did = s.sdid
-WHEN MATCHED AND dnotes <> '' THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge '
-WHEN MATCHED THEN
-	DELETE;
-
--- Switch back to regress_rls_bob role
-RESET SESSION AUTHORIZATION;
-SET SESSION AUTHORIZATION regress_rls_bob;
-
--- Try INSERT action. This fails because we are trying to insert
--- dauthor = regress_rls_dave and INSERT's WITH CHECK does not allow
--- that
-MERGE INTO document d
-USING (SELECT 12 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	DELETE
-WHEN NOT MATCHED THEN
-	INSERT VALUES (12, 11, 1, 'regress_rls_dave', 'another novel');
-
--- This should be fine
-MERGE INTO document d
-USING (SELECT 12 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	DELETE
-WHEN NOT MATCHED THEN
-	INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
-
--- ok
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge4 '
-WHEN NOT MATCHED THEN
-	INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
-
--- drop and create a new SELECT policy which prevents us from reading
--- any document except with category 'magna'
-RESET SESSION AUTHORIZATION;
-DROP POLICY p1 ON document;
-CREATE POLICY p1 ON document FOR SELECT
-  USING (cid = (SELECT cid from category WHERE cname = 'manga'));
-
-SET SESSION AUTHORIZATION regress_rls_bob;
-
--- MERGE can no longer see the matching row and hence attempts the
--- NOT MATCHED action, which results in unique key violation
-MERGE INTO document d
-USING (SELECT 1 as sdid) s
-ON did = s.sdid
-WHEN MATCHED THEN
-	UPDATE SET dnotes = dnotes || ' notes added by merge5 '
-WHEN NOT MATCHED THEN
-	INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
-
-RESET SESSION AUTHORIZATION;
--- drop the restrictive SELECT policy so that we can look at the
--- final state of the table
-DROP POLICY p1 ON document;
--- Just check everything went per plan
-SELECT * FROM document;
-
 --
 -- ROLE/GROUP
 --
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index b866268892..a82f52d154 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1191,39 +1191,6 @@ CREATE RULE rules_parted_table_insert AS ON INSERT to rules_parted_table
 ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
 DROP TABLE rules_parted_table;
 
---
--- test MERGE
---
-CREATE TABLE rule_merge1 (a int, b text);
-CREATE TABLE rule_merge2 (a int, b text);
-CREATE RULE rule1 AS ON INSERT TO rule_merge1
-	DO INSTEAD INSERT INTO rule_merge2 VALUES (NEW.*);
-CREATE RULE rule2 AS ON UPDATE TO rule_merge1
-	DO INSTEAD UPDATE rule_merge2 SET a = NEW.a, b = NEW.b
-	WHERE a = OLD.a;
-CREATE RULE rule3 AS ON DELETE TO rule_merge1
-	DO INSTEAD DELETE FROM rule_merge2 WHERE a = OLD.a;
-
--- MERGE not supported for table with rules
-MERGE INTO rule_merge1 t USING (SELECT 1 AS a) s
-	ON t.a = s.a
-	WHEN MATCHED AND t.a < 2 THEN
-		UPDATE SET b = b || ' updated by merge'
-	WHEN MATCHED AND t.a > 2 THEN
-		DELETE
-	WHEN NOT MATCHED THEN
-		INSERT VALUES (s.a, '');
-
--- should be ok with the other table though
-MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
-	ON t.a = s.a
-	WHEN MATCHED AND t.a < 2 THEN
-		UPDATE SET b = b || ' updated by merge'
-	WHEN MATCHED AND t.a > 2 THEN
-		DELETE
-	WHEN NOT MATCHED THEN
-		INSERT VALUES (s.a, '');
-
 --
 -- Test enabling/disabling
 --
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index b51c884eee..c6f31dd8c8 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2110,53 +2110,6 @@ delete from self_ref where a = 1;
 
 drop table self_ref;
 
---
--- test transition tables with MERGE
---
-create table merge_target_table (a int primary key, b text);
-create trigger merge_target_table_insert_trig
-  after insert on merge_target_table referencing new table as new_table
-  for each statement execute procedure dump_insert();
-create trigger merge_target_table_update_trig
-  after update on merge_target_table referencing old table as old_table new table as new_table
-  for each statement execute procedure dump_update();
-create trigger merge_target_table_delete_trig
-  after delete on merge_target_table referencing old table as old_table
-  for each statement execute procedure dump_delete();
-
-create table merge_source_table (a int, b text);
-insert into merge_source_table
-  values (1, 'initial1'), (2, 'initial2'),
-		 (3, 'initial3'), (4, 'initial4');
-
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when not matched then
-  insert values (a, b);
-
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when matched and s.a <= 2 then
-	update set b = t.b || ' updated by merge'
-when matched and s.a > 2 then
-	delete
-when not matched then
-  insert values (a, b);
-
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when matched and s.a <= 2 then
-	update set b = t.b || ' updated again by merge'
-when matched and s.a > 2 then
-	delete
-when not matched then
-  insert values (a, b);
-
-drop table merge_source_table, merge_target_table;
-
 -- cleanup
 drop function dump_insert();
 drop function dump_update();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6d8a44cd9e..abc10a8ffd 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1228,8 +1228,6 @@ MemoryContextCallbackFunction
 MemoryContextCounters
 MemoryContextData
 MemoryContextMethods
-MergeAction
-MergeActionState
 MergeAppend
 MergeAppendPath
 MergeAppendState
@@ -1237,7 +1235,6 @@ MergeJoin
 MergeJoinClause
 MergeJoinState
 MergePath
-MergeStmt
 MergeScanSelCache
 MetaCommand
 MinMaxAggInfo