Support MERGE into updatable views.
authorDean Rasheed <dean.a.rasheed@gmail.com>
Thu, 29 Feb 2024 15:56:59 +0000 (15:56 +0000)
committerDean Rasheed <dean.a.rasheed@gmail.com>
Thu, 29 Feb 2024 15:56:59 +0000 (15:56 +0000)
This allows the target relation of MERGE to be an auto-updatable or
trigger-updatable view, and includes support for WITH CHECK OPTION,
security barrier views, and security invoker views.

A trigger-updatable view must have INSTEAD OF triggers for every type
of action (INSERT, UPDATE, and DELETE) mentioned in the MERGE command.
An auto-updatable view must not have any INSTEAD OF triggers. Mixing
auto-update and trigger-update actions (i.e., having a partial set of
INSTEAD OF triggers) is not supported.

Rule-updatable views are also not supported, since there is no
rewriter support for non-SELECT rules with MERGE operations.

Dean Rasheed, reviewed by Jian He and Alvaro Herrera.

Discussion: https://postgr.es/m/CAEZATCVcB1g0nmxuEc-A+gGB0HnfcGQNGYH7gS=7rq0u0zOBXA@mail.gmail.com

23 files changed:
doc/src/sgml/ref/create_view.sgml
doc/src/sgml/ref/merge.sgml
doc/src/sgml/rules.sgml
src/backend/commands/copyfrom.c
src/backend/executor/execMain.c
src/backend/executor/execPartition.c
src/backend/executor/nodeModifyTable.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/appendinfo.c
src/backend/parser/parse_merge.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rewriteManip.c
src/bin/psql/tab-complete.c
src/include/catalog/catversion.h
src/include/executor/executor.h
src/include/nodes/parsenodes.h
src/include/rewrite/rewriteHandler.h
src/test/regress/expected/merge.out
src/test/regress/expected/rules.out
src/test/regress/expected/updatable_views.out
src/test/regress/sql/merge.sql
src/test/regress/sql/rules.sql
src/test/regress/sql/updatable_views.sql

index 3b26205f7883ce8c081612c3df3176e46f85ec03..e8d9d3c8d0f64afd34a982390c2245d93014b5a2 100644 (file)
@@ -192,12 +192,14 @@ CREATE VIEW [ <replaceable>schema</replaceable> . ] <replaceable>view_name</repl
     <listitem>
      <para>
       This option controls the behavior of automatically updatable views.  When
-      this option is specified, <command>INSERT</command> and <command>UPDATE</command>
+      this option is specified, <command>INSERT</command>,
+      <command>UPDATE</command>, and <command>MERGE</command>
       commands on the view will be checked to ensure that new rows satisfy the
       view-defining condition (that is, the new rows are checked to ensure that
       they are visible through the view).  If they are not, the update will be
       rejected.  If the <literal>CHECK OPTION</literal> is not specified,
-      <command>INSERT</command> and <command>UPDATE</command> commands on the view are
+      <command>INSERT</command>, <command>UPDATE</command>, and
+      <command>MERGE</command> commands on the view are
       allowed to create rows that are not visible through the view.  The
       following check options are supported:
 
@@ -247,7 +249,8 @@ CREATE VIEW [ <replaceable>schema</replaceable> . ] <replaceable>view_name</repl
       <command>INSERT</command> or <command>UPDATE</command> command to be rewritten, then
       all check options will be ignored in the rewritten query, including any
       checks from automatically updatable views defined on top of the relation
-      with the <literal>INSTEAD</literal> rule.
+      with the <literal>INSTEAD</literal> rule.  <command>MERGE</command> is not
+      supported if the view or any of its base relations have rules.
      </para>
     </listitem>
    </varlistentry>
@@ -360,7 +363,8 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
 
    <para>
     Simple views are automatically updatable: the system will allow
-    <command>INSERT</command>, <command>UPDATE</command> and <command>DELETE</command> statements
+    <command>INSERT</command>, <command>UPDATE</command>,
+    <command>DELETE</command>, and <command>MERGE</command> statements
     to be used on the view in the same way as on a regular table.  A view is
     automatically updatable if it satisfies all of the following conditions:
 
@@ -400,13 +404,15 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
     An automatically updatable view may contain a mix of updatable and
     non-updatable columns.  A column is updatable if it is a simple reference
     to an updatable column of the underlying base relation; otherwise the
-    column is read-only, and an error will be raised if an <command>INSERT</command>
-    or <command>UPDATE</command> statement attempts to assign a value to it.
+    column is read-only, and an error will be raised if an
+    <command>INSERT</command>, <command>UPDATE</command>, or
+    <command>MERGE</command> statement attempts to assign a value to it.
    </para>
 
    <para>
     If the view is automatically updatable the system will convert any
-    <command>INSERT</command>, <command>UPDATE</command> or <command>DELETE</command> statement
+    <command>INSERT</command>, <command>UPDATE</command>,
+    <command>DELETE</command>, or <command>MERGE</command> statement
     on the view into the corresponding statement on the underlying base
     relation.  <command>INSERT</command> statements that have an <literal>ON
     CONFLICT UPDATE</literal> clause are fully supported.
@@ -415,17 +421,21 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
    <para>
     If an automatically updatable view contains a <literal>WHERE</literal>
     condition, the condition restricts which rows of the base relation are
-    available to be modified by <command>UPDATE</command> and <command>DELETE</command>
-    statements on the view.  However, an <command>UPDATE</command> is allowed to
+    available to be modified by <command>UPDATE</command>,
+    <command>DELETE</command>, and <command>MERGE</command>
+    statements on the view.  However, an <command>UPDATE</command> or
+    <command>MERGE</command> is allowed to
     change a row so that it no longer satisfies the <literal>WHERE</literal>
     condition, and thus is no longer visible through the view.  Similarly,
-    an <command>INSERT</command> command can potentially insert base-relation rows
+    an <command>INSERT</command> or <command>MERGE</command> command can
+    potentially insert base-relation rows
     that do not satisfy the <literal>WHERE</literal> condition and thus are not
     visible through the view (<literal>ON CONFLICT UPDATE</literal> may
     similarly affect an existing row not visible through the view).
     The <literal>CHECK OPTION</literal> may be used to prevent
-    <command>INSERT</command> and <command>UPDATE</command> commands from creating
-    such rows that are not visible through the view.
+    <command>INSERT</command>, <command>UPDATE</command>, and
+    <command>MERGE</command> commands from creating such rows that are not
+    visible through the view.
    </para>
 
    <para>
@@ -443,14 +453,16 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
 
    <para>
     A more complex view that does not satisfy all these conditions is
-    read-only by default: the system will not allow an insert, update, or
-    delete on the view.  You can get the effect of an updatable view by
+    read-only by default: the system will not allow an <command>INSERT</command>,
+    <command>UPDATE</command>, <command>DELETE</command>, or <command>MERGE</command>
+    on the view.  You can get the effect of an updatable view by
     creating <literal>INSTEAD OF</literal> triggers on the view, which must
     convert attempted inserts, etc. on the view into appropriate actions
     on other tables.  For more information see <xref
     linkend="sql-createtrigger"/>.  Another possibility is to create rules
     (see <xref linkend="sql-createrule"/>), but in practice triggers are
-    easier to understand and use correctly.
+    easier to understand and use correctly.  Also note that <command>MERGE</command>
+    is not supported on relations with rules.
    </para>
 
    <para>
index bb34ef9b921bd00dfabc75f471324ac38cd7c4b0..7a8ac40259ce1eeba5741e9e5efb236fb1577554 100644 (file)
@@ -132,9 +132,9 @@ DELETE
     <term><replaceable class="parameter">target_table_name</replaceable></term>
     <listitem>
      <para>
-      The name (optionally schema-qualified) of the target table to merge into.
-      If <literal>ONLY</literal> is specified before the table name, matching
-      rows are updated or deleted in the named table only.  If
+      The name (optionally schema-qualified) of the target table or view to
+      merge into.  If <literal>ONLY</literal> is specified before a table
+      name, matching rows are updated or deleted in the named table only.  If
       <literal>ONLY</literal> is not specified, matching rows are also updated
       or deleted in any tables inheriting from the named table.  Optionally,
       <literal>*</literal> can be specified after the table name to explicitly
@@ -142,6 +142,16 @@ DELETE
       <literal>ONLY</literal> keyword and <literal>*</literal> option do not
       affect insert actions, which always insert into the named table only.
      </para>
+
+     <para>
+      If <replaceable class="parameter">target_table_name</replaceable> is a
+      view, it must either be automatically updatable with no
+      <literal>INSTEAD OF</literal> triggers, or it must have
+      <literal>INSTEAD OF</literal> triggers for every type of action
+      (<literal>INSERT</literal>, <literal>UPDATE</literal>, and
+      <literal>DELETE</literal>) specified in the <literal>WHEN</literal>
+      clauses.  Views with rules are not supported.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -486,7 +496,11 @@ MERGE <replaceable class="parameter">total_count</replaceable>
              the action's event type.
             </para>
            </listitem>
-          </orderedlist></para>
+          </orderedlist>
+          If the target relation is a view with <literal>INSTEAD OF ROW</literal>
+          triggers for the action's event type, they are used to perform the
+          action instead.
+         </para>
         </listitem>
        </orderedlist></para>
      </listitem>
index d229b94d396b055e781c1ad00778ec7e499965a8..784c16e76ea0129d6747477248fc310909b0ed1a 100644 (file)
@@ -797,9 +797,9 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
 
 <para>
     What happens if a view is named as the target relation for an
-    <command>INSERT</command>, <command>UPDATE</command>, or
-    <command>DELETE</command>?  Doing the substitutions
-    described above would give a query tree in which the result
+    <command>INSERT</command>, <command>UPDATE</command>,
+    <command>DELETE</command>, or <command>MERGE</command>?  Doing the
+    substitutions described above would give a query tree in which the result
     relation points at a subquery range-table entry, which will not
     work.  There are several ways in which <productname>PostgreSQL</productname>
     can support the appearance of updating a view, however.
@@ -813,11 +813,12 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
     If the subquery selects from a single base relation and is simple
     enough, the rewriter can automatically replace the subquery with the
     underlying base relation so that the <command>INSERT</command>,
-    <command>UPDATE</command>, or <command>DELETE</command> is applied to
-    the base relation in the appropriate way.  Views that are
-    <quote>simple enough</quote> for this are called <firstterm>automatically
-    updatable</firstterm>.  For detailed information on the kinds of view that can
-    be automatically updated, see <xref linkend="sql-createview"/>.
+    <command>UPDATE</command>, <command>DELETE</command>, or
+    <command>MERGE</command> is applied to the base relation in the
+    appropriate way.  Views that are <quote>simple enough</quote> for this
+    are called <firstterm>automatically updatable</firstterm>.  For detailed
+    information on the kinds of view that can be automatically updated, see
+    <xref linkend="sql-createview"/>.
 </para>
 
 <para>
@@ -827,10 +828,10 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
     Rewriting works slightly differently
     in this case.  For <command>INSERT</command>, the rewriter does
     nothing at all with the view, leaving it as the result relation
-    for the query.  For <command>UPDATE</command> and
-    <command>DELETE</command>, it's still necessary to expand the
+    for the query.  For <command>UPDATE</command>, <command>DELETE</command>,
+    and <command>MERGE</command>, it's still necessary to expand the
     view query to produce the <quote>old</quote> rows that the command will
-    attempt to update or delete.  So the view is expanded as normal,
+    attempt to update, delete, or merge.  So the view is expanded as normal,
     but another unexpanded range-table entry is added to the query
     to represent the view in its capacity as the result relation.
 </para>
@@ -842,13 +843,13 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
     list to identify the physical locations of the rows to be updated.
     This does not work if the result relation is a view, because a view
     does not have any <acronym>CTID</acronym>, since its rows do not have
-    actual physical locations. Instead, for an <command>UPDATE</command>
-    or <command>DELETE</command> operation, a special <literal>wholerow</literal>
-    entry is added to the target list, which expands to include all
-    columns from the view. The executor uses this value to supply the
-    <quote>old</quote> row to the <literal>INSTEAD OF</literal> trigger.  It is
-    up to the trigger to work out what to update based on the old and
-    new row values.
+    actual physical locations. Instead, for an <command>UPDATE</command>,
+    <command>DELETE</command>, or <command>MERGE</command> operation, a
+    special <literal>wholerow</literal> entry is added to the target list,
+    which expands to include all columns from the view. The executor uses this
+    value to supply the <quote>old</quote> row to the
+    <literal>INSTEAD OF</literal> trigger.  It is up to the trigger to work
+    out what to update based on the old and new row values.
 </para>
 
 <para>
@@ -857,7 +858,8 @@ SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
     <command>UPDATE</command>, and <command>DELETE</command> commands on
     a view. These rules will rewrite the command, typically into a command
     that updates one or more tables, rather than views. That is the topic
-    of <xref linkend="rules-update"/>.
+    of <xref linkend="rules-update"/>.  Note that this will not work with
+    <command>MERGE</command>, which currently does not support rules.
 </para>
 
 <para>
index 1fe70b913382760c52097557b9a3e4e4b9d773f4..c3bc897028a6180fe3dabc41dde74ea35b0d2643 100644 (file)
@@ -767,7 +767,7 @@ CopyFrom(CopyFromState cstate)
        ExecInitResultRelation(estate, resultRelInfo, 1);
 
        /* Verify the named relation is a valid target for INSERT */
-       CheckValidResultRel(resultRelInfo, CMD_INSERT);
+       CheckValidResultRel(resultRelInfo, CMD_INSERT, NIL);
 
        ExecOpenIndices(resultRelInfo, false);
 
index 13a9b7da83b28a9f2647aac9f21702ecd94be773..79ef46f2614193c7f76ee53f51dad5d0d82787c4 100644 (file)
@@ -56,6 +56,7 @@
 #include "miscadmin.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "tcop/utility.h"
@@ -1017,14 +1018,18 @@ InitPlan(QueryDesc *queryDesc, int eflags)
  * Generally the parser and/or planner should have noticed any such mistake
  * already, but let's make sure.
  *
+ * For MERGE, mergeActions is the list of actions that may be performed.  The
+ * result relation is required to support every action, regardless of whether
+ * or not they are all executed.
+ *
  * Note: when changing this function, you probably also need to look at
  * CheckValidRowMarkRel.
  */
 void
-CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
+CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
+                                       List *mergeActions)
 {
        Relation        resultRel = resultRelInfo->ri_RelationDesc;
-       TriggerDesc *trigDesc = resultRel->trigdesc;
        FdwRoutine *fdwroutine;
 
        switch (resultRel->rd_rel->relkind)
@@ -1048,42 +1053,14 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
                case RELKIND_VIEW:
 
                        /*
-                        * Okay only if there's a suitable INSTEAD OF trigger.  Messages
-                        * here should match rewriteHandler.c's rewriteTargetView and
-                        * RewriteQuery, except that we omit errdetail because we haven't
-                        * got the information handy (and given that we really shouldn't
-                        * get here anyway, it's not worth great exertion to get).
+                        * Okay only if there's a suitable INSTEAD OF trigger.  Otherwise,
+                        * complain, but omit errdetail because we haven't got the
+                        * information handy (and given that it really shouldn't happen,
+                        * it's not worth great exertion to get).
                         */
-                       switch (operation)
-                       {
-                               case CMD_INSERT:
-                                       if (!trigDesc || !trigDesc->trig_insert_instead_row)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                                errmsg("cannot insert into view \"%s\"",
-                                                                               RelationGetRelationName(resultRel)),
-                                                                errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.")));
-                                       break;
-                               case CMD_UPDATE:
-                                       if (!trigDesc || !trigDesc->trig_update_instead_row)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                                errmsg("cannot update view \"%s\"",
-                                                                               RelationGetRelationName(resultRel)),
-                                                                errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.")));
-                                       break;
-                               case CMD_DELETE:
-                                       if (!trigDesc || !trigDesc->trig_delete_instead_row)
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                                errmsg("cannot delete from view \"%s\"",
-                                                                               RelationGetRelationName(resultRel)),
-                                                                errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.")));
-                                       break;
-                               default:
-                                       elog(ERROR, "unrecognized CmdType: %d", (int) operation);
-                                       break;
-                       }
+                       if (!view_has_instead_trigger(resultRel, operation, mergeActions))
+                               error_view_not_updatable(resultRel, operation, mergeActions,
+                                                                                NULL);
                        break;
                case RELKIND_MATVIEW:
                        if (!MatViewIncrementalMaintenanceIsEnabled())
index b22040ae8ee2b78ecf72e39db991fb8a8f57364f..429f0a8dd984bfcccc5f1d701d00e529170aee78 100644 (file)
@@ -361,7 +361,7 @@ ExecFindPartition(ModifyTableState *mtstate,
                                if (rri)
                                {
                                        /* Verify this ResultRelInfo allows INSERTs */
-                                       CheckValidResultRel(rri, CMD_INSERT);
+                                       CheckValidResultRel(rri, CMD_INSERT, NIL);
 
                                        /*
                                         * Initialize information needed to insert this and
@@ -527,7 +527,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
         * partition-key becomes a DELETE+INSERT operation, so this check is still
         * required when the operation is CMD_UPDATE.
         */
-       CheckValidResultRel(leaf_part_rri, CMD_INSERT);
+       CheckValidResultRel(leaf_part_rri, CMD_INSERT, NIL);
 
        /*
         * Open partition indices.  The user may have asked to check for conflicts
index 098ed4026b60096fa8f6ce6bce0c2c672b0fb20e..ff7ec8419bbd83756e615fb4b6f9811f723800b2 100644 (file)
@@ -150,11 +150,13 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
 static TupleTableSlot *ExecMerge(ModifyTableContext *context,
                                                                 ResultRelInfo *resultRelInfo,
                                                                 ItemPointer tupleid,
+                                                                HeapTuple oldtuple,
                                                                 bool canSetTag);
 static void ExecInitMerge(ModifyTableState *mtstate, EState *estate);
 static bool ExecMergeMatched(ModifyTableContext *context,
                                                         ResultRelInfo *resultRelInfo,
                                                         ItemPointer tupleid,
+                                                        HeapTuple oldtuple,
                                                         bool canSetTag);
 static void ExecMergeNotMatched(ModifyTableContext *context,
                                                                ResultRelInfo *resultRelInfo,
@@ -2712,13 +2714,14 @@ ExecOnConflictUpdate(ModifyTableContext *context,
  */
 static TupleTableSlot *
 ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-                 ItemPointer tupleid, bool canSetTag)
+                 ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag)
 {
        bool            matched;
 
        /*-----
-        * If we are dealing with a WHEN MATCHED case (tupleid is valid), we
-        * execute the first action for which the additional WHEN MATCHED AND
+        * If we are dealing with a WHEN MATCHED case (tupleid or oldtuple is
+        * valid, depending on whether the result relation is a table or a view),
+        * we execute the first action for which the additional WHEN MATCHED AND
         * quals pass.  If an action without quals is found, that action is
         * executed.
         *
@@ -2759,9 +2762,10 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
         * from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a
         * livelock.
         */
-       matched = tupleid != NULL;
+       matched = tupleid != NULL || oldtuple != NULL;
        if (matched)
-               matched = ExecMergeMatched(context, resultRelInfo, tupleid, canSetTag);
+               matched = ExecMergeMatched(context, resultRelInfo, tupleid, oldtuple,
+                                                                  canSetTag);
 
        /*
         * Either we were dealing with a NOT MATCHED tuple or ExecMergeMatched()
@@ -2776,8 +2780,10 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 }
 
 /*
- * Check and execute the first qualifying MATCHED action. The current target
- * tuple is identified by tupleid.
+ * Check and execute the first qualifying MATCHED action.  If the target
+ * relation is a table, the current target tuple is identified by tupleid.
+ * Otherwise, if the target relation is a view, oldtuple is the current target
+ * tuple from the view.
  *
  * We start from the first WHEN MATCHED action and check if the WHEN quals
  * pass, if any. If the WHEN quals for the first action do not pass, we
@@ -2798,7 +2804,7 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
  */
 static bool
 ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-                                ItemPointer tupleid, bool canSetTag)
+                                ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag)
 {
        ModifyTableState *mtstate = context->mtstate;
        TupleTableSlot *newslot;
@@ -2824,22 +2830,33 @@ ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
        econtext->ecxt_innertuple = context->planSlot;
        econtext->ecxt_outertuple = NULL;
 
+       /*
+        * This routine is only invoked for matched rows, so we should either have
+        * the tupleid of the target row, or an old tuple from the target wholerow
+        * junk attr.
+        */
+       Assert(tupleid != NULL || oldtuple != NULL);
+       if (oldtuple != NULL)
+               ExecForceStoreHeapTuple(oldtuple, resultRelInfo->ri_oldTupleSlot,
+                                                               false);
+
 lmerge_matched:
 
        /*
-        * This routine is only invoked for matched rows, and we must have found
-        * the tupleid of the target row in that case; fetch that tuple.
+        * If passed a tupleid, use it to fetch the old target row.
         *
         * We use SnapshotAny for this because we might get called again after
         * EvalPlanQual returns us a new tuple, which may not be visible to our
         * MVCC snapshot.
         */
-
-       if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
-                                                                          tupleid,
-                                                                          SnapshotAny,
-                                                                          resultRelInfo->ri_oldTupleSlot))
-               elog(ERROR, "failed to fetch the target tuple");
+       if (tupleid != NULL)
+       {
+               if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
+                                                                                  tupleid,
+                                                                                  SnapshotAny,
+                                                                                  resultRelInfo->ri_oldTupleSlot))
+                       elog(ERROR, "failed to fetch the target tuple");
+       }
 
        foreach(l, resultRelInfo->ri_matchedMergeAction)
        {
@@ -2899,20 +2916,33 @@ lmerge_matched:
                                                return true;    /* "do nothing" */
                                        break;          /* concurrent update/delete */
                                }
-                               result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL,
-                                                                          newslot, canSetTag, &updateCxt);
 
-                               /*
-                                * As in ExecUpdate(), if ExecUpdateAct() reports that a
-                                * cross-partition update was done, then there's nothing else
-                                * for us to do --- the UPDATE has been turned into a DELETE
-                                * and an INSERT, and we must not perform any of the usual
-                                * post-update tasks.
-                                */
-                               if (updateCxt.crossPartUpdate)
+                               /* INSTEAD OF ROW UPDATE Triggers */
+                               if (resultRelInfo->ri_TrigDesc &&
+                                       resultRelInfo->ri_TrigDesc->trig_update_instead_row)
                                {
-                                       mtstate->mt_merge_updated += 1;
-                                       return true;
+                                       if (!ExecIRUpdateTriggers(estate, resultRelInfo,
+                                                                                         oldtuple, newslot))
+                                               return true;    /* "do nothing" */
+                               }
+                               else
+                               {
+                                       result = ExecUpdateAct(context, resultRelInfo, tupleid,
+                                                                                  NULL, newslot, canSetTag,
+                                                                                  &updateCxt);
+
+                                       /*
+                                        * As in ExecUpdate(), if ExecUpdateAct() reports that a
+                                        * cross-partition update was done, then there's nothing
+                                        * else for us to do --- the UPDATE has been turned into a
+                                        * DELETE and an INSERT, and we must not perform any of
+                                        * the usual post-update tasks.
+                                        */
+                                       if (updateCxt.crossPartUpdate)
+                                       {
+                                               mtstate->mt_merge_updated += 1;
+                                               return true;
+                                       }
                                }
 
                                if (result == TM_Ok)
@@ -2932,7 +2962,19 @@ lmerge_matched:
                                                return true;    /* "do nothing" */
                                        break;          /* concurrent update/delete */
                                }
-                               result = ExecDeleteAct(context, resultRelInfo, tupleid, false);
+
+                               /* INSTEAD OF ROW DELETE Triggers */
+                               if (resultRelInfo->ri_TrigDesc &&
+                                       resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
+                               {
+                                       if (!ExecIRDeleteTriggers(estate, resultRelInfo,
+                                                                                         oldtuple))
+                                               return true;    /* "do nothing" */
+                               }
+                               else
+                                       result = ExecDeleteAct(context, resultRelInfo, tupleid,
+                                                                                  false);
+
                                if (result == TM_Ok)
                                {
                                        ExecDeleteEpilogue(context, resultRelInfo, tupleid, NULL,
@@ -3663,7 +3705,8 @@ ExecModifyTable(PlanState *pstate)
                                {
                                        EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
 
-                                       ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag);
+                                       ExecMerge(&context, node->resultRelInfo, NULL, NULL,
+                                                         node->canSetTag);
                                        continue;       /* no RETURNING support yet */
                                }
 
@@ -3741,7 +3784,8 @@ ExecModifyTable(PlanState *pstate)
                                        {
                                                EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
 
-                                               ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag);
+                                               ExecMerge(&context, node->resultRelInfo, NULL, NULL,
+                                                                 node->canSetTag);
                                                continue;       /* no RETURNING support yet */
                                        }
 
@@ -3774,9 +3818,28 @@ ExecModifyTable(PlanState *pstate)
                                datum = ExecGetJunkAttribute(slot,
                                                                                         resultRelInfo->ri_RowIdAttNo,
                                                                                         &isNull);
-                               /* shouldn't ever get a null result... */
+
+                               /*
+                                * For commands other than MERGE, any tuples having a null row
+                                * identifier are errors.  For MERGE, we may need to handle
+                                * them as WHEN NOT MATCHED clauses if any, so do that.
+                                *
+                                * Note that we use the node's toplevel resultRelInfo, not any
+                                * specific partition's.
+                                */
                                if (isNull)
+                               {
+                                       if (operation == CMD_MERGE)
+                                       {
+                                               EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
+
+                                               ExecMerge(&context, node->resultRelInfo, NULL, NULL,
+                                                                 node->canSetTag);
+                                               continue;       /* no RETURNING support yet */
+                                       }
+
                                        elog(ERROR, "wholerow is NULL");
+                               }
 
                                oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
                                oldtupdata.t_len =
@@ -3847,7 +3910,8 @@ ExecModifyTable(PlanState *pstate)
                                break;
 
                        case CMD_MERGE:
-                               slot = ExecMerge(&context, resultRelInfo, tupleid, node->canSetTag);
+                               slot = ExecMerge(&context, resultRelInfo, tupleid, oldtuple,
+                                                                node->canSetTag);
                                break;
 
                        default:
@@ -4025,6 +4089,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
        foreach(l, node->resultRelations)
        {
                Index           resultRelation = lfirst_int(l);
+               List       *mergeActions = NIL;
+
+               if (node->mergeActionLists)
+                       mergeActions = list_nth(node->mergeActionLists, i);
 
                if (resultRelInfo != mtstate->rootResultRelInfo)
                {
@@ -4046,7 +4114,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
                /*
                 * Verify result relation is a valid target for the current operation
                 */
-               CheckValidResultRel(resultRelInfo, operation);
+               CheckValidResultRel(resultRelInfo, operation, mergeActions);
 
                resultRelInfo++;
                i++;
@@ -4122,8 +4190,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
                        }
                        else
                        {
-                               /* No support for MERGE */
-                               Assert(operation != CMD_MERGE);
                                /* Other valid target relkinds must provide wholerow */
                                resultRelInfo->ri_RowIdAttNo =
                                        ExecFindJunkAttributeInTlist(subplan->targetlist,
index aa83dd3636fe89dafac35170d250baafd0d7e1c5..300691cc4db15c4c94bd26ee69a75886ebcc7b8b 100644 (file)
@@ -198,12 +198,17 @@ transform_MERGE_to_join(Query *parse)
 
        /*
         * Create a JOIN between the target and the source relation.
+        *
+        * Here the target is identified by parse->mergeTargetRelation.  For a
+        * regular table, this will equal parse->resultRelation, but for a
+        * trigger-updatable view, it will be the expanded view subquery that we
+        * need to pull data from.
         */
        joinexpr = makeNode(JoinExpr);
        joinexpr->jointype = jointype;
        joinexpr->isNatural = false;
        joinexpr->larg = (Node *) makeNode(RangeTblRef);
-       ((RangeTblRef *) joinexpr->larg)->rtindex = parse->resultRelation;
+       ((RangeTblRef *) joinexpr->larg)->rtindex = parse->mergeTargetRelation;
        joinexpr->rarg = linitial(parse->jointree->fromlist);   /* original join */
        joinexpr->usingClause = NIL;
        joinexpr->join_using_alias = NULL;
@@ -215,6 +220,19 @@ transform_MERGE_to_join(Query *parse)
        /* Make the new join be the sole entry in the query's jointree */
        parse->jointree->fromlist = list_make1(joinexpr);
        parse->jointree->quals = NULL;
+
+       /*
+        * If necessary, mark parse->targetlist entries that refer to the target
+        * as nullable by the join.  Normally the targetlist will be empty for a
+        * MERGE, but if the target is a trigger-updatable view, it will contain a
+        * whole-row Var referring to the expanded view query.
+        */
+       if (parse->targetList != NIL &&
+               (jointype == JOIN_RIGHT || jointype == JOIN_FULL))
+               parse->targetList = (List *)
+                       add_nulling_relids((Node *) parse->targetList,
+                                                          bms_make_singleton(parse->mergeTargetRelation),
+                                                          bms_make_singleton(joinrti));
 }
 
 /*
index 51fdeace7d233944230039f67679ff784860644d..6ba4eba224a5834ccfb87b601fc5d7f6711dd890 100644 (file)
@@ -891,8 +891,7 @@ add_row_identity_columns(PlannerInfo *root, Index rtindex,
 
        Assert(commandType == CMD_UPDATE || commandType == CMD_DELETE || commandType == CMD_MERGE);
 
-       if (commandType == CMD_MERGE ||
-               relkind == RELKIND_RELATION ||
+       if (relkind == RELKIND_RELATION ||
                relkind == RELKIND_MATVIEW ||
                relkind == RELKIND_PARTITIONED_TABLE)
        {
index 73f7a48b3c69beb10eea1682db4e4962990fb965..4356d61f8ede4af474693064f4d53b8add8b816a 100644 (file)
@@ -172,28 +172,27 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
         * Set up the MERGE target table.  The target table is added to the
         * namespace below and to joinlist in transform_MERGE_to_join, so don't do
         * it here.
+        *
+        * Initially mergeTargetRelation is the same as resultRelation, so data is
+        * read from the table being updated.  However, that might be changed by
+        * the rewriter, if the target is a trigger-updatable view, to allow
+        * target data to be read from the expanded view query while updating the
+        * original view relation.
         */
        qry->resultRelation = setTargetTable(pstate, stmt->relation,
                                                                                 stmt->relation->inh,
                                                                                 false, targetPerms);
+       qry->mergeTargetRelation = qry->resultRelation;
 
-       /*
-        * MERGE is unsupported in various cases
-        */
+       /* The target relation must be a table or a view */
        if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
-               pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+               pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
+               pstate->p_target_relation->rd_rel->relkind != RELKIND_VIEW)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("cannot execute MERGE on relation \"%s\"",
                                                RelationGetRelationName(pstate->p_target_relation)),
                                 errdetail_relkind_not_supported(pstate->p_target_relation->rd_rel->relkind)));
-       if (pstate->p_target_relation->rd_rules != NULL &&
-               pstate->p_target_relation->rd_rules->numLocks > 0)
-               ereport(ERROR,
-                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                errmsg("cannot execute MERGE on relation \"%s\"",
-                                               RelationGetRelationName(pstate->p_target_relation)),
-                                errdetail("MERGE is not supported for relations with rules.")));
 
        /* Now transform the source relation to produce the source RTE. */
        transformFromClause(pstate,
index f60b34deb640db353c94b174c9c14a5412bc5d1a..b8839b56b7ad6185cee118182f5b597c33c78f1f 100644 (file)
@@ -87,10 +87,9 @@ static void rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte);
 static void markQueryForLocking(Query *qry, Node *jtnode,
                                                                LockClauseStrength strength, LockWaitPolicy waitPolicy,
                                                                bool pushedDown);
-static List *matchLocks(CmdType event, RuleLock *rulelocks,
+static List *matchLocks(CmdType event, Relation relation,
                                                int varno, Query *parsetree, bool *hasUpdate);
 static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
-static bool view_has_instead_trigger(Relation view, CmdType event);
 static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist);
 
 
@@ -1482,7 +1481,7 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
         */
        isAutoUpdatableView = false;
        if (target_relation->rd_rel->relkind == RELKIND_VIEW &&
-               !view_has_instead_trigger(target_relation, CMD_INSERT))
+               !view_has_instead_trigger(target_relation, CMD_INSERT, NIL))
        {
                List       *locks;
                bool            hasUpdate;
@@ -1490,7 +1489,7 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
                ListCell   *l;
 
                /* Look for an unconditional DO INSTEAD rule */
-               locks = matchLocks(CMD_INSERT, target_relation->rd_rules,
+               locks = matchLocks(CMD_INSERT, target_relation,
                                                   parsetree->resultRelation, parsetree, &hasUpdate);
 
                found = false;
@@ -1654,15 +1653,16 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte)
 
 /*
  * matchLocks -
- *       match the list of locks and returns the matching rules
+ *       match a relation's list of locks and returns the matching rules
  */
 static List *
 matchLocks(CmdType event,
-                  RuleLock *rulelocks,
+                  Relation relation,
                   int varno,
                   Query *parsetree,
                   bool *hasUpdate)
 {
+       RuleLock   *rulelocks = relation->rd_rules;
        List       *matching_locks = NIL;
        int                     nlocks;
        int                     i;
@@ -1670,10 +1670,6 @@ matchLocks(CmdType event,
        if (rulelocks == NULL)
                return NIL;
 
-       /* No rule support for MERGE */
-       if (parsetree->commandType == CMD_MERGE)
-               return NIL;
-
        if (parsetree->commandType != CMD_SELECT)
        {
                if (parsetree->resultRelation != varno)
@@ -1691,7 +1687,7 @@ matchLocks(CmdType event,
 
                /*
                 * Suppress ON INSERT/UPDATE/DELETE rules that are disabled or
-                * configured to not fire during the current sessions replication
+                * configured to not fire during the current session's replication
                 * role. ON SELECT rules will always be applied in order to keep views
                 * working even in LOCAL or REPLICA role.
                 */
@@ -1709,6 +1705,14 @@ matchLocks(CmdType event,
                                        oneLock->enabled == RULE_DISABLED)
                                        continue;
                        }
+
+                       /* Non-SELECT rules are not supported for MERGE */
+                       if (parsetree->commandType == CMD_MERGE)
+                               ereport(ERROR,
+                                               errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                               errmsg("cannot execute MERGE on relation \"%s\"",
+                                                          RelationGetRelationName(relation)),
+                                               errdetail("MERGE is not supported for relations with rules."));
                }
 
                if (oneLock->event == event)
@@ -1755,9 +1759,9 @@ ApplyRetrieveRule(Query *parsetree,
                 * For INSERT, we needn't do anything.  The unmodified RTE will serve
                 * fine as the result relation.
                 *
-                * For UPDATE/DELETE, we need to expand the view so as to have source
-                * data for the operation.  But we also need an unmodified RTE to
-                * serve as the target.  So, copy the RTE and add the copy to the
+                * For UPDATE/DELETE/MERGE, we need to expand the view so as to have
+                * source data for the operation.  But we also need an unmodified RTE
+                * to serve as the target.  So, copy the RTE and add the copy to the
                 * rangetable.  Note that the copy does not get added to the jointree.
                 * Also note that there's a hack in fireRIRrules to avoid calling this
                 * function again when it arrives at the copied RTE.
@@ -1765,7 +1769,8 @@ ApplyRetrieveRule(Query *parsetree,
                if (parsetree->commandType == CMD_INSERT)
                        return parsetree;
                else if (parsetree->commandType == CMD_UPDATE ||
-                                parsetree->commandType == CMD_DELETE)
+                                parsetree->commandType == CMD_DELETE ||
+                                parsetree->commandType == CMD_MERGE)
                {
                        RangeTblEntry *newrte;
                        Var                *var;
@@ -1775,6 +1780,7 @@ ApplyRetrieveRule(Query *parsetree,
                        newrte = copyObject(rte);
                        parsetree->rtable = lappend(parsetree->rtable, newrte);
                        parsetree->resultRelation = list_length(parsetree->rtable);
+                       /* parsetree->mergeTargetRelation unchanged (use expanded view) */
 
                        /*
                         * For the most part, Vars referencing the view should remain as
@@ -2470,9 +2476,15 @@ get_view_query(Relation view)
  * If it does, we don't want to treat it as auto-updatable.  This test can't
  * be folded into view_query_is_auto_updatable because it's not an error
  * condition.
+ *
+ * For MERGE, this will return true if there is an INSTEAD OF trigger for
+ * every action in mergeActionList, and false if there are any actions that
+ * lack an INSTEAD OF trigger.  If there are no data-modifying MERGE actions
+ * (only DO NOTHING actions), true is returned so that the view is treated
+ * as trigger-updatable, rather than erroring out if it's not auto-updatable.
  */
-static bool
-view_has_instead_trigger(Relation view, CmdType event)
+bool
+view_has_instead_trigger(Relation view, CmdType event, List *mergeActionList)
 {
        TriggerDesc *trigDesc = view->trigdesc;
 
@@ -2490,6 +2502,32 @@ view_has_instead_trigger(Relation view, CmdType event)
                        if (trigDesc && trigDesc->trig_delete_instead_row)
                                return true;
                        break;
+               case CMD_MERGE:
+                       foreach_node(MergeAction, action, mergeActionList)
+                       {
+                               switch (action->commandType)
+                               {
+                                       case CMD_INSERT:
+                                               if (!trigDesc || !trigDesc->trig_insert_instead_row)
+                                                       return false;
+                                               break;
+                                       case CMD_UPDATE:
+                                               if (!trigDesc || !trigDesc->trig_update_instead_row)
+                                                       return false;
+                                               break;
+                                       case CMD_DELETE:
+                                               if (!trigDesc || !trigDesc->trig_delete_instead_row)
+                                                       return false;
+                                               break;
+                                       case CMD_NOTHING:
+                                               /* No trigger required */
+                                               break;
+                                       default:
+                                               elog(ERROR, "unrecognized commandType: %d", action->commandType);
+                                               break;
+                               }
+                       }
+                       return true;            /* no actions without an INSTEAD OF trigger */
                default:
                        elog(ERROR, "unrecognized CmdType: %d", (int) event);
                        break;
@@ -3030,6 +3068,105 @@ adjust_view_column_set(Bitmapset *cols, List *targetlist)
 }
 
 
+/*
+ * error_view_not_updatable -
+ *       Report an error due to an attempt to update a non-updatable view.
+ *
+ * Generally this is expected to be called from the rewriter, with suitable
+ * error detail explaining why the view is not updatable.  Note, however, that
+ * the executor also performs a just-in-case check that the target view is
+ * updatable.  That check is expected to never fail, but if it does, it will
+ * call this function with NULL error detail --- see CheckValidResultRel().
+ *
+ * Note: for MERGE, at least one of the actions in mergeActionList is expected
+ * to lack a suitable INSTEAD OF trigger --- see view_has_instead_trigger().
+ */
+void
+error_view_not_updatable(Relation view,
+                                                CmdType command,
+                                                List *mergeActionList,
+                                                const char *detail)
+{
+       TriggerDesc *trigDesc = view->trigdesc;
+
+       switch (command)
+       {
+               case CMD_INSERT:
+                       ereport(ERROR,
+                                       errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                       errmsg("cannot insert into view \"%s\"",
+                                                  RelationGetRelationName(view)),
+                                       detail ? errdetail_internal("%s", _(detail)) : 0,
+                                       errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule."));
+                       break;
+               case CMD_UPDATE:
+                       ereport(ERROR,
+                                       errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                       errmsg("cannot update view \"%s\"",
+                                                  RelationGetRelationName(view)),
+                                       detail ? errdetail_internal("%s", _(detail)) : 0,
+                                       errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule."));
+                       break;
+               case CMD_DELETE:
+                       ereport(ERROR,
+                                       errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                       errmsg("cannot delete from view \"%s\"",
+                                                  RelationGetRelationName(view)),
+                                       detail ? errdetail_internal("%s", _(detail)) : 0,
+                                       errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule."));
+                       break;
+               case CMD_MERGE:
+
+                       /*
+                        * Note that the error hints here differ from above, since MERGE
+                        * doesn't support rules.
+                        */
+                       foreach_node(MergeAction, action, mergeActionList)
+                       {
+                               switch (action->commandType)
+                               {
+                                       case CMD_INSERT:
+                                               if (!trigDesc || !trigDesc->trig_insert_instead_row)
+                                                       ereport(ERROR,
+                                                                       errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                                       errmsg("cannot insert into view \"%s\"",
+                                                                                  RelationGetRelationName(view)),
+                                                                       detail ? errdetail_internal("%s", _(detail)) : 0,
+                                                                       errhint("To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger."));
+                                               break;
+                                       case CMD_UPDATE:
+                                               if (!trigDesc || !trigDesc->trig_update_instead_row)
+                                                       ereport(ERROR,
+                                                                       errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                                       errmsg("cannot update view \"%s\"",
+                                                                                  RelationGetRelationName(view)),
+                                                                       detail ? errdetail_internal("%s", _(detail)) : 0,
+                                                                       errhint("To enable updating the view using MERGE, provide an INSTEAD OF UPDATE trigger."));
+                                               break;
+                                       case CMD_DELETE:
+                                               if (!trigDesc || !trigDesc->trig_delete_instead_row)
+                                                       ereport(ERROR,
+                                                                       errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                                       errmsg("cannot delete from view \"%s\"",
+                                                                                  RelationGetRelationName(view)),
+                                                                       detail ? errdetail_internal("%s", _(detail)) : 0,
+                                                                       errhint("To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger."));
+                                               break;
+                                       case CMD_NOTHING:
+                                               break;
+                                       default:
+                                               elog(ERROR, "unrecognized commandType: %d", action->commandType);
+                                               break;
+                               }
+                       }
+                       break;
+               default:
+                       elog(ERROR, "unrecognized CmdType: %d", (int) command);
+                       break;
+       }
+}
+
+
 /*
  * rewriteTargetView -
  *       Attempt to rewrite a query where the target relation is a view, so that
@@ -3043,6 +3180,7 @@ static Query *
 rewriteTargetView(Query *parsetree, Relation view)
 {
        Query      *viewquery;
+       bool            insert_or_update;
        const char *auto_update_detail;
        RangeTblRef *rtr;
        int                     base_rt_index;
@@ -3066,55 +3204,52 @@ rewriteTargetView(Query *parsetree, Relation view)
         */
        viewquery = copyObject(get_view_query(view));
 
-       /* The view must be updatable, else fail */
-       auto_update_detail =
-               view_query_is_auto_updatable(viewquery,
-                                                                        parsetree->commandType != CMD_DELETE);
+       /*
+        * Are we doing INSERT/UPDATE, or MERGE containing INSERT/UPDATE?  If so,
+        * various additional checks on the view columns need to be applied, and
+        * any view CHECK OPTIONs need to be enforced.
+        */
+       insert_or_update =
+               (parsetree->commandType == CMD_INSERT ||
+                parsetree->commandType == CMD_UPDATE);
 
-       if (auto_update_detail)
+       if (parsetree->commandType == CMD_MERGE)
        {
-               /* messages here should match execMain.c's CheckValidResultRel */
-               switch (parsetree->commandType)
+               foreach_node(MergeAction, action, parsetree->mergeActionList)
                {
-                       case CMD_INSERT:
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                errmsg("cannot insert into view \"%s\"",
-                                                               RelationGetRelationName(view)),
-                                                errdetail_internal("%s", _(auto_update_detail)),
-                                                errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.")));
-                               break;
-                       case CMD_UPDATE:
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                errmsg("cannot update view \"%s\"",
-                                                               RelationGetRelationName(view)),
-                                                errdetail_internal("%s", _(auto_update_detail)),
-                                                errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.")));
-                               break;
-                       case CMD_DELETE:
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                errmsg("cannot delete from view \"%s\"",
-                                                               RelationGetRelationName(view)),
-                                                errdetail_internal("%s", _(auto_update_detail)),
-                                                errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.")));
-                               break;
-                       default:
-                               elog(ERROR, "unrecognized CmdType: %d",
-                                        (int) parsetree->commandType);
+                       if (action->commandType == CMD_INSERT ||
+                               action->commandType == CMD_UPDATE)
+                       {
+                               insert_or_update = true;
                                break;
+                       }
                }
        }
 
        /*
-        * For INSERT/UPDATE the modified columns must all be updatable. Note that
-        * we get the modified columns from the query's targetlist, not from the
-        * result RTE's insertedCols and/or updatedCols set, since
-        * rewriteTargetListIU may have added additional targetlist entries for
-        * view defaults, and these must also be updatable.
+        * The view must be updatable, else fail.
+        *
+        * If we are doing INSERT/UPDATE (or MERGE containing INSERT/UPDATE), we
+        * also check that there is at least one updatable column.
         */
-       if (parsetree->commandType != CMD_DELETE)
+       auto_update_detail =
+               view_query_is_auto_updatable(viewquery, insert_or_update);
+
+       if (auto_update_detail)
+               error_view_not_updatable(view,
+                                                                parsetree->commandType,
+                                                                parsetree->mergeActionList,
+                                                                auto_update_detail);
+
+       /*
+        * For INSERT/UPDATE (or MERGE containing INSERT/UPDATE) the modified
+        * columns must all be updatable. Note that we get the modified columns
+        * from the query's targetlist, not from the result RTE's insertedCols
+        * and/or updatedCols set, since rewriteTargetListIU may have added
+        * additional targetlist entries for view defaults, and these must also be
+        * updatable.
+        */
+       if (insert_or_update)
        {
                Bitmapset  *modified_cols = NULL;
                char       *non_updatable_col;
@@ -3140,6 +3275,20 @@ rewriteTargetView(Query *parsetree, Relation view)
                        }
                }
 
+               foreach_node(MergeAction, action, parsetree->mergeActionList)
+               {
+                       if (action->commandType == CMD_INSERT ||
+                               action->commandType == CMD_UPDATE)
+                       {
+                               foreach_node(TargetEntry, tle, action->targetList)
+                               {
+                                       if (!tle->resjunk)
+                                               modified_cols = bms_add_member(modified_cols,
+                                                                                                          tle->resno - FirstLowInvalidHeapAttributeNumber);
+                               }
+                       }
+               }
+
                auto_update_detail = view_cols_are_auto_updatable(viewquery,
                                                                                                                  modified_cols,
                                                                                                                  NULL,
@@ -3168,6 +3317,14 @@ rewriteTargetView(Query *parsetree, Relation view)
                                                                        RelationGetRelationName(view)),
                                                         errdetail_internal("%s", _(auto_update_detail))));
                                        break;
+                               case CMD_MERGE:
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                        errmsg("cannot merge into column \"%s\" of view \"%s\"",
+                                                                       non_updatable_col,
+                                                                       RelationGetRelationName(view)),
+                                                        errdetail_internal("%s", _(auto_update_detail))));
+                                       break;
                                default:
                                        elog(ERROR, "unrecognized CmdType: %d",
                                                 (int) parsetree->commandType);
@@ -3176,6 +3333,28 @@ rewriteTargetView(Query *parsetree, Relation view)
                }
        }
 
+       /*
+        * For MERGE, there must not be any INSTEAD OF triggers on an otherwise
+        * updatable view.  The caller already checked that there isn't a full set
+        * of INSTEAD OF triggers, so this is to guard against having a partial
+        * set (mixing auto-update and trigger-update actions in a single command
+        * isn't supported).
+        */
+       if (parsetree->commandType == CMD_MERGE)
+       {
+               foreach_node(MergeAction, action, parsetree->mergeActionList)
+               {
+                       if (action->commandType != CMD_NOTHING &&
+                               view_has_instead_trigger(view, action->commandType, NIL))
+                               ereport(ERROR,
+                                               errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                               errmsg("cannot merge into view \"%s\"",
+                                                          RelationGetRelationName(view)),
+                                               errdetail("MERGE is not supported for views with INSTEAD OF triggers for some actions, but not others."),
+                                               errhint("To enable merging into the view, either provide a full set of INSTEAD OF triggers or drop the existing INSTEAD OF triggers."));
+               }
+       }
+
        /* Locate RTE describing the view in the outer query */
        view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable);
 
@@ -3239,8 +3418,8 @@ rewriteTargetView(Query *parsetree, Relation view)
        new_rt_index = list_length(parsetree->rtable);
 
        /*
-        * INSERTs never inherit.  For UPDATE/DELETE, we use the view query's
-        * inheritance flag for the base relation.
+        * INSERTs never inherit.  For UPDATE/DELETE/MERGE, we use the view
+        * query's inheritance flag for the base relation.
         */
        if (parsetree->commandType == CMD_INSERT)
                new_rte->inh = false;
@@ -3362,11 +3541,12 @@ rewriteTargetView(Query *parsetree, Relation view)
        /*
         * For INSERT/UPDATE we must also update resnos in the targetlist to refer
         * to columns of the base relation, since those indicate the target
-        * columns to be affected.
+        * columns to be affected.  Similarly, for MERGE we must update the resnos
+        * in the merge action targetlists of any INSERT/UPDATE actions.
         *
-        * Note that this destroys the resno ordering of the targetlist, but that
+        * Note that this destroys the resno ordering of the targetlists, but that
         * will be fixed when we recurse through RewriteQuery, which will invoke
-        * rewriteTargetListIU again on the updated targetlist.
+        * rewriteTargetListIU again on the updated targetlists.
         */
        if (parsetree->commandType != CMD_DELETE)
        {
@@ -3385,6 +3565,28 @@ rewriteTargetView(Query *parsetree, Relation view)
                                elog(ERROR, "attribute number %d not found in view targetlist",
                                         tle->resno);
                }
+
+               foreach_node(MergeAction, action, parsetree->mergeActionList)
+               {
+                       if (action->commandType == CMD_INSERT ||
+                               action->commandType == CMD_UPDATE)
+                       {
+                               foreach_node(TargetEntry, tle, action->targetList)
+                               {
+                                       TargetEntry *view_tle;
+
+                                       if (tle->resjunk)
+                                               continue;
+
+                                       view_tle = get_tle_by_resno(view_targetlist, tle->resno);
+                                       if (view_tle != NULL && !view_tle->resjunk && IsA(view_tle->expr, Var))
+                                               tle->resno = ((Var *) view_tle->expr)->varattno;
+                                       else
+                                               elog(ERROR, "attribute number %d not found in view targetlist",
+                                                        tle->resno);
+                               }
+                       }
+               }
        }
 
        /*
@@ -3477,10 +3679,10 @@ rewriteTargetView(Query *parsetree, Relation view)
        }
 
        /*
-        * For UPDATE/DELETE, pull up any WHERE quals from the view.  We know that
-        * any Vars in the quals must reference the one base relation, so we need
-        * only adjust their varnos to reference the new target (just the same as
-        * we did with the view targetlist).
+        * For UPDATE/DELETE/MERGE, pull up any WHERE quals from the view.  We
+        * know that any Vars in the quals must reference the one base relation,
+        * so we need only adjust their varnos to reference the new target (just
+        * the same as we did with the view targetlist).
         *
         * If it's a security-barrier view, its WHERE quals must be applied before
         * quals from the outer query, so we attach them to the RTE as security
@@ -3532,11 +3734,12 @@ rewriteTargetView(Query *parsetree, Relation view)
        }
 
        /*
-        * For INSERT/UPDATE, if the view has the WITH CHECK OPTION, or any parent
-        * view specified WITH CASCADED CHECK OPTION, add the quals from the view
-        * to the query's withCheckOptions list.
+        * For INSERT/UPDATE (or MERGE containing INSERT/UPDATE), if the view has
+        * the WITH CHECK OPTION, or any parent view specified WITH CASCADED CHECK
+        * OPTION, add the quals from the view to the query's withCheckOptions
+        * list.
         */
-       if (parsetree->commandType != CMD_DELETE)
+       if (insert_or_update)
        {
                bool            has_wco = RelationHasCheckOption(view);
                bool            cascaded = RelationHasCascadedCheckOption(view);
@@ -3590,14 +3793,13 @@ rewriteTargetView(Query *parsetree, Relation view)
                                ChangeVarNodes(wco->qual, base_rt_index, new_rt_index, 0);
 
                                /*
-                                * Make sure that the query is marked correctly if the added
-                                * qual has sublinks.  We can skip this check if the query is
-                                * already marked, or if the command is an UPDATE, in which
-                                * case the same qual will have already been added, and this
-                                * check will already have been done.
+                                * For INSERT, make sure that the query is marked correctly if
+                                * the added qual has sublinks.  This can be skipped for
+                                * UPDATE/MERGE, since the same qual will have already been
+                                * added above, and the check will already have been done.
                                 */
                                if (!parsetree->hasSubLinks &&
-                                       parsetree->commandType != CMD_UPDATE)
+                                       parsetree->commandType == CMD_INSERT)
                                        parsetree->hasSubLinks = checkExprHasSubLink(wco->qual);
                        }
                }
@@ -3867,7 +4069,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
                /*
                 * Collect and apply the appropriate rules.
                 */
-               locks = matchLocks(event, rt_entry_relation->rd_rules,
+               locks = matchLocks(event, rt_entry_relation,
                                                   result_relation, parsetree, &hasUpdate);
 
                product_orig_rt_length = list_length(parsetree->rtable);
@@ -3938,7 +4140,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
                 * automatically updated.  If so, we perform the necessary query
                 * transformation here and add the resulting query to the
                 * product_queries list, so that it gets recursively rewritten if
-                * necessary.
+                * necessary.  For MERGE, the view must be automatically updatable if
+                * any of the merge actions lack a corresponding INSTEAD OF trigger.
                 *
                 * If the view cannot be automatically updated, we throw an error here
                 * which is OK since the query would fail at runtime anyway.  Throwing
@@ -3948,51 +4151,19 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
                 */
                if (!instead &&
                        rt_entry_relation->rd_rel->relkind == RELKIND_VIEW &&
-                       !view_has_instead_trigger(rt_entry_relation, event))
+                       !view_has_instead_trigger(rt_entry_relation, event,
+                                                                         parsetree->mergeActionList))
                {
                        /*
                         * If there were any qualified INSTEAD rules, don't allow the view
                         * to be automatically updated (an unqualified INSTEAD rule or
                         * INSTEAD OF trigger is required).
-                        *
-                        * The messages here should match execMain.c's CheckValidResultRel
-                        * and in principle make those checks in executor unnecessary, but
-                        * we keep them just in case.
                         */
                        if (qual_product != NULL)
-                       {
-                               switch (parsetree->commandType)
-                               {
-                                       case CMD_INSERT:
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                                errmsg("cannot insert into view \"%s\"",
-                                                                               RelationGetRelationName(rt_entry_relation)),
-                                                                errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."),
-                                                                errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.")));
-                                               break;
-                                       case CMD_UPDATE:
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                                errmsg("cannot update view \"%s\"",
-                                                                               RelationGetRelationName(rt_entry_relation)),
-                                                                errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."),
-                                                                errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.")));
-                                               break;
-                                       case CMD_DELETE:
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                                errmsg("cannot delete from view \"%s\"",
-                                                                               RelationGetRelationName(rt_entry_relation)),
-                                                                errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."),
-                                                                errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.")));
-                                               break;
-                                       default:
-                                               elog(ERROR, "unrecognized CmdType: %d",
-                                                        (int) parsetree->commandType);
-                                               break;
-                               }
-                       }
+                               error_view_not_updatable(rt_entry_relation,
+                                                                                parsetree->commandType,
+                                                                                parsetree->mergeActionList,
+                                                                                gettext_noop("Views with conditional DO INSTEAD rules are not automatically updatable."));
 
                        /*
                         * Attempt to rewrite the query to automatically update the view.
index 76c97a5b28edaa1255a672ab8ed9be4f5a1b2f75..191f2dc0b1d8d00beb06aedb0f7e2c2af48464fa 100644 (file)
@@ -496,9 +496,10 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
                /*
                 * If we are starting at a Query, and sublevels_up is zero, then we
                 * must also fix rangetable indexes in the Query itself --- namely
-                * resultRelation, exclRelIndex and rowMarks entries.  sublevels_up
-                * cannot be zero when recursing into a subquery, so there's no need
-                * to have the same logic inside OffsetVarNodes_walker.
+                * resultRelation, mergeTargetRelation, exclRelIndex and rowMarks
+                * entries.  sublevels_up cannot be zero when recursing into a
+                * subquery, so there's no need to have the same logic inside
+                * OffsetVarNodes_walker.
                 */
                if (sublevels_up == 0)
                {
@@ -507,6 +508,9 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
                        if (qry->resultRelation)
                                qry->resultRelation += offset;
 
+                       if (qry->mergeTargetRelation)
+                               qry->mergeTargetRelation += offset;
+
                        if (qry->onConflict && qry->onConflict->exclRelIndex)
                                qry->onConflict->exclRelIndex += offset;
 
@@ -687,9 +691,10 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
                /*
                 * If we are starting at a Query, and sublevels_up is zero, then we
                 * must also fix rangetable indexes in the Query itself --- namely
-                * resultRelation and rowMarks entries.  sublevels_up cannot be zero
-                * when recursing into a subquery, so there's no need to have the same
-                * logic inside ChangeVarNodes_walker.
+                * resultRelation, mergeTargetRelation, exclRelIndex  and rowMarks
+                * entries.  sublevels_up cannot be zero when recursing into a
+                * subquery, so there's no need to have the same logic inside
+                * ChangeVarNodes_walker.
                 */
                if (sublevels_up == 0)
                {
@@ -698,6 +703,9 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
                        if (qry->resultRelation == rt_index)
                                qry->resultRelation = new_index;
 
+                       if (qry->mergeTargetRelation == rt_index)
+                               qry->mergeTargetRelation = new_index;
+
                        /* this is unlikely to ever be used, but ... */
                        if (qry->onConflict && qry->onConflict->exclRelIndex == rt_index)
                                qry->onConflict->exclRelIndex = new_index;
index 151a5211ee4a20a4a90cd806fe0ef40ea0e810af..aa1acf85235e62397ce10870864b7eb7b8b3b493 100644 (file)
@@ -832,6 +832,7 @@ static const SchemaQuery Query_for_list_of_mergetargets = {
        .catname = "pg_catalog.pg_class c",
        .selcondition =
        "c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+       CppAsString2(RELKIND_VIEW) ", "
        CppAsString2(RELKIND_PARTITIONED_TABLE) ") ",
        .viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
        .namespace = "c.relnamespace",
index 61beae92e2821261db438d94b7aa034d9e0228a2..7faa6a9bb7cf1db5cd7563ee48cbe6edeb7b90e4 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202402142
+#define CATALOG_VERSION_NO     202402291
 
 #endif
index 5e8c335a7379a660df58e6b1f8198cd7a9f3ac7b..9770752ea3c1bdb8b446ecf732f332f39a4aafbe 100644 (file)
@@ -210,7 +210,8 @@ extern void standard_ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
 extern bool ExecCheckPermissions(List *rangeTable,
                                                                 List *rteperminfos, bool ereport_on_violation);
-extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation);
+extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
+                                                               List *mergeActions);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
                                                          Relation resultRelationDesc,
                                                          Index resultRelationIndex,
index baa6a97c7e267e5f9fa49632b23d822debfb061a..d60e148ff262e1f0a26927eaad73d7b53bd843bc 100644 (file)
@@ -178,6 +178,14 @@ typedef struct Query
        /* whether to use outer join */
        bool            mergeUseOuterJoin pg_node_attr(query_jumble_ignore);
 
+       /*
+        * rtable index of target relation for MERGE to pull data. Initially, this
+        * is the same as resultRelation, but after query rewriting, if the target
+        * relation is a trigger-updatable view, this is the index of the expanded
+        * view subquery, whereas resultRelation is the index of the target view.
+        */
+       int                     mergeTargetRelation pg_node_attr(query_jumble_ignore);
+
        List       *targetList;         /* target list (of TargetEntry) */
 
        /* OVERRIDING clause */
index 14d3f3017a2902f27337155fd00cdbdbd2ee9fa4..1b65cda71cfc7ee6e3df011e66d2ab3c62e46517 100644 (file)
@@ -25,11 +25,17 @@ extern void AcquireRewriteLocks(Query *parsetree,
 extern Node *build_column_default(Relation rel, int attrno);
 
 extern Query *get_view_query(Relation view);
+extern bool view_has_instead_trigger(Relation view, CmdType event,
+                                                                        List *mergeActionList);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
                                                                                                bool check_cols);
 extern int     relation_is_updatable(Oid reloid,
                                                                  List *outer_reloids,
                                                                  bool include_triggers,
                                                                  Bitmapset *include_cols);
+extern void error_view_not_updatable(Relation view,
+                                                                        CmdType command,
+                                                                        List *mergeActionList,
+                                                                        const char *detail);
 
 #endif                                                 /* REWRITEHANDLER_H */
index 28a69802d7297e96eadd24d58c1c668c7a5e35d4..e3ebf46e61112038ab26309882f6769ab6ada2c6 100644 (file)
@@ -139,16 +139,6 @@ COPY (
 ) TO stdout;
 ERROR:  MERGE not supported in COPY
 -- unsupported relation types
--- view
-CREATE VIEW tv AS SELECT * FROM target;
-MERGE INTO tv t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
-       INSERT DEFAULT VALUES;
-ERROR:  cannot execute MERGE on relation "tv"
-DETAIL:  This operation is not supported for views.
-DROP VIEW tv;
 -- materialized view
 CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
 MERGE INTO mv t
index b7488d760e5ea74ad8302fd955fd40721d64d214..0cd2c64fca10d60c4094d3ff7520e6bd82f69387 100644 (file)
@@ -3623,6 +3623,18 @@ MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
                DELETE
        WHEN NOT MATCHED THEN
                INSERT VALUES (s.a, '');
+-- also ok if the rules are disabled
+ALTER TABLE rule_merge1 DISABLE RULE rule1;
+ALTER TABLE rule_merge1 DISABLE RULE rule2;
+ALTER TABLE rule_merge1 DISABLE RULE rule3;
+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, '');
 -- test deparsing
 CREATE TABLE sf_target(id int, data text, filling int[]);
 CREATE FUNCTION merge_sf_test()
index 1950e6f281f5f729eae12be95024f39490e53bef..794cf9cf930474ca742a1be50604bc77db38d8d0 100644 (file)
@@ -184,6 +184,24 @@ INSERT INTO ro_view13 VALUES (3, 'Row 3');
 ERROR:  cannot insert into view "ro_view13"
 DETAIL:  Views that do not select from a single table or view are not automatically updatable.
 HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
+MERGE INTO ro_view13 AS t USING (VALUES (1, 'Row 1')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN DELETE;
+ERROR:  cannot delete from view "ro_view13"
+DETAIL:  Views that do not select from a single table or view are not automatically updatable.
+HINT:  To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger.
+MERGE INTO ro_view13 AS t USING (VALUES (2, 'Row 2')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = v.b;
+ERROR:  cannot update view "ro_view13"
+DETAIL:  Views that do not select from a single table or view are not automatically updatable.
+HINT:  To enable updating the view using MERGE, provide an INSTEAD OF UPDATE trigger.
+MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b);
+ERROR:  cannot insert into view "ro_view13"
+DETAIL:  Views that do not select from a single table or view are not automatically updatable.
+HINT:  To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger.
+MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN DO NOTHING
+  WHEN NOT MATCHED THEN DO NOTHING; -- should be OK to do nothing
 -- Partially updatable view
 INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
 ERROR:  cannot insert into column "ctid" of view "rw_view14"
@@ -205,6 +223,41 @@ SELECT * FROM base_tbl;
 (6 rows)
 
 DELETE FROM rw_view14 WHERE a=3; -- should be OK
+MERGE INTO rw_view14  AS t
+  USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = v.b  -- should be OK, except...
+  WHEN NOT MATCHED THEN INSERT VALUES (null, v.a, v.b); -- should fail
+ERROR:  cannot merge into column "ctid" of view "rw_view14"
+DETAIL:  View columns that refer to system columns are not updatable.
+MERGE INTO rw_view14  AS t
+  USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = v.b  -- should be OK
+  WHEN NOT MATCHED THEN INSERT (a,b) VALUES (v.a, v.b); -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
+ a  |      b       
+----+--------------
+ -2 | Row -2
+ -1 | Row -1
+  0 | Row 0
+  1 | Row 1
+  2 | Merged row 2
+  3 | Merged row 3
+(6 rows)
+
+MERGE INTO rw_view14  AS t
+  USING (VALUES (2, 'Row 2'), (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED AND t.a = 2 THEN UPDATE SET b = v.b  -- should be OK
+  WHEN MATCHED AND t.a = 3 THEN DELETE; -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
+ a  |   b    
+----+--------
+ -2 | Row -2
+ -1 | Row -1
+  0 | Row 0
+  1 | Row 1
+  2 | Row 2
+(5 rows)
+
 -- Partially updatable view
 INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
 ERROR:  cannot insert into column "upper" of view "rw_view15"
@@ -351,6 +404,10 @@ DELETE FROM rw_view16 WHERE a=2; -- should fail
 ERROR:  cannot delete from view "rw_view16"
 DETAIL:  Views with conditional DO INSTEAD rules are not automatically updatable.
 HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
+MERGE INTO rw_view16 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
+ERROR:  cannot execute MERGE on relation "rw_view16"
+DETAIL:  MERGE is not supported for relations with rules.
 DROP TABLE base_tbl CASCADE;
 NOTICE:  drop cascades to 16 other objects
 DETAIL:  drop cascades to view ro_view1
@@ -417,6 +474,23 @@ SELECT * FROM base_tbl;
   5 | Unspecified
 (6 rows)
 
+MERGE INTO rw_view1 t
+  USING (VALUES (0, 'ROW 0'), (1, 'ROW 1'),
+                (2, 'ROW 2'), (3, 'ROW 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED AND t.a <= 1 THEN UPDATE SET b = v.b
+  WHEN MATCHED THEN DELETE
+  WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a);
+SELECT * FROM base_tbl ORDER BY a;
+ a  |      b      
+----+-------------
+ -2 | Row -2
+ -1 | Row -1
+  0 | Row 0
+  1 | ROW 1
+  2 | Unspecified
+  5 | Unspecified
+(6 rows)
+
 EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
                     QUERY PLAN                    
 --------------------------------------------------
@@ -433,6 +507,50 @@ EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5;
          Index Cond: ((a > 0) AND (a = 5))
 (3 rows)
 
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t USING (VALUES (5, 'X')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN DELETE;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Merge on base_tbl
+   ->  Index Scan using base_tbl_pkey on base_tbl
+         Index Cond: ((a > 0) AND (a = 5))
+(3 rows)
+
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+  USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = 'Updated';
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Merge on base_tbl
+   ->  Hash Join
+         Hash Cond: (base_tbl.a = generate_series.generate_series)
+         ->  Bitmap Heap Scan on base_tbl
+               Recheck Cond: (a > 0)
+               ->  Bitmap Index Scan on base_tbl_pkey
+                     Index Cond: (a > 0)
+         ->  Hash
+               ->  Function Scan on generate_series
+(9 rows)
+
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+  USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
+  WHEN NOT MATCHED THEN INSERT (a) VALUES (s.a);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Merge on base_tbl
+   ->  Hash Right Join
+         Hash Cond: (base_tbl.a = generate_series.generate_series)
+         ->  Bitmap Heap Scan on base_tbl
+               Recheck Cond: (a > 0)
+               ->  Bitmap Index Scan on base_tbl_pkey
+                     Index Cond: (a > 0)
+         ->  Hash
+               ->  Function Scan on generate_series
+(9 rows)
+
 -- it's still updatable if we add a DO ALSO rule
 CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
 CREATE RULE base_tbl_log AS ON INSERT TO rw_view1 DO ALSO
@@ -509,6 +627,19 @@ SELECT * FROM rw_view2;
    4 | Row 4
 (3 rows)
 
+MERGE INTO rw_view2 t
+  USING (VALUES (3, 'R3'), (4, 'R4'), (5, 'R5')) AS v(a,b) ON aaa = v.a
+  WHEN MATCHED AND aaa = 3 THEN DELETE
+  WHEN MATCHED THEN UPDATE SET bbb = v.b
+  WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a);
+SELECT * FROM rw_view2 ORDER BY aaa;
+ aaa |     bbb     
+-----+-------------
+   1 | Row 1
+   4 | R4
+   5 | Unspecified
+(3 rows)
+
 EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
                        QUERY PLAN                       
 --------------------------------------------------------
@@ -701,6 +832,10 @@ SELECT * FROM rw_view2;
  2 | Row 2
 (2 rows)
 
+MERGE INTO rw_view2 t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
+ERROR:  cannot execute MERGE on relation "rw_view1"
+DETAIL:  MERGE is not supported for relations with rules.
 EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
                            QUERY PLAN                           
 ----------------------------------------------------------------
@@ -932,6 +1067,21 @@ SELECT * FROM rw_view2;
  2 | Row 2
 (2 rows)
 
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED AND t.a <= 1 THEN DELETE
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+SELECT * FROM base_tbl ORDER BY a;
+ a  |   b    
+----+--------
+ -2 | Row -2
+ -1 | Row -1
+  0 | Row 0
+  2 | R2
+  3 | R3
+(5 rows)
+
 EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
                         QUERY PLAN                        
 ----------------------------------------------------------
@@ -956,6 +1106,76 @@ EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
                      Index Cond: (a > 0)
 (7 rows)
 
+EXPLAIN (costs off)
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED AND t.a <= 1 THEN DELETE
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Merge on rw_view1 rw_view1_1
+   ->  Hash Right Join
+         Hash Cond: (rw_view1.a = x.x)
+         ->  Subquery Scan on rw_view1
+               Filter: (rw_view1.a < 10)
+               ->  Bitmap Heap Scan on base_tbl
+                     Recheck Cond: (a > 0)
+                     ->  Bitmap Index Scan on base_tbl_pkey
+                           Index Cond: (a > 0)
+         ->  Hash
+               ->  Function Scan on generate_series x
+(11 rows)
+
+-- MERGE with incomplete set of INSTEAD OF triggers
+DROP TRIGGER rw_view1_del_trig ON rw_view1;
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED AND t.a <= 1 THEN DELETE
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+ERROR:  cannot delete from view "rw_view1"
+DETAIL:  Views containing LIMIT or OFFSET are not automatically updatable.
+HINT:  To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger.
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- ok
+DROP TRIGGER rw_view1_ins_trig ON rw_view1;
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+ERROR:  cannot insert into view "rw_view1"
+DETAIL:  Views containing LIMIT or OFFSET are not automatically updatable.
+HINT:  To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger.
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
+-- MERGE with INSTEAD OF triggers on auto-updatable view
+CREATE TRIGGER rw_view2_upd_trig INSTEAD OF UPDATE ON rw_view2
+  FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+ERROR:  cannot merge into view "rw_view2"
+DETAIL:  MERGE is not supported for views with INSTEAD OF triggers for some actions, but not others.
+HINT:  To enable merging into the view, either provide a full set of INSTEAD OF triggers or drop the existing INSTEAD OF triggers.
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
+SELECT * FROM base_tbl ORDER BY a;
+ a  |   b    
+----+--------
+ -2 | Row -2
+ -1 | Row -1
+  0 | Row 0
+  1 | R1
+  2 | R2
+  3 | R3
+(6 rows)
+
 DROP TABLE base_tbl CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to view rw_view1
@@ -1041,6 +1261,14 @@ INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
 ERROR:  permission denied for view rw_view1
 INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t
+  USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t
+  USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+ERROR:  permission denied for table base_tbl
 UPDATE base_tbl SET a=a, c=c; -- ok
 UPDATE base_tbl SET b=b; -- not allowed
 ERROR:  permission denied for table base_tbl
@@ -1050,6 +1278,16 @@ ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
 UPDATE rw_view2 SET bb=bb; -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET bb = bb, cc = cc; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa, cc = cc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
+ERROR:  permission denied for table base_tbl
 DELETE FROM base_tbl; -- not allowed
 ERROR:  permission denied for table base_tbl
 DELETE FROM rw_view1; -- not allowed
@@ -1069,6 +1307,11 @@ DELETE FROM base_tbl WHERE a=1; -- ok
 DELETE FROM rw_view1 WHERE aa=2; -- not allowed
 ERROR:  permission denied for view rw_view1
 DELETE FROM rw_view2 WHERE aa=2; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
 SELECT * FROM base_tbl;
  a |   b   | c 
 ---+-------+---
@@ -1092,6 +1335,11 @@ ERROR:  permission denied for table base_tbl
 DELETE FROM rw_view1 WHERE aa=3; -- ok
 DELETE FROM rw_view2 WHERE aa=4; -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
+ERROR:  permission denied for table base_tbl
 SELECT * FROM base_tbl;
  a |   b   | c 
 ---+-------+---
@@ -1115,6 +1363,9 @@ SELECT * FROM rw_view1 FOR UPDATE;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view1 SET b = 'foo' WHERE a = 1;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user2;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1;
 SELECT * FROM rw_view2;  -- not allowed
@@ -1123,6 +1374,9 @@ SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
 ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR:  permission denied for view rw_view1
 RESET SESSION AUTHORIZATION;
 GRANT SELECT ON base_tbl TO regress_view_user1;
 SET SESSION AUTHORIZATION regress_view_user1;
@@ -1136,6 +1390,9 @@ SELECT * FROM rw_view1 FOR UPDATE;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view1 SET b = 'foo' WHERE a = 1;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;  -- not allowed
 ERROR:  permission denied for view rw_view1
@@ -1143,6 +1400,9 @@ SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
 ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR:  permission denied for view rw_view1
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT SELECT ON rw_view1 TO regress_view_user2;
 SET SESSION AUTHORIZATION regress_view_user2;
@@ -1156,6 +1416,9 @@ SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
 ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR:  permission denied for view rw_view1
 RESET SESSION AUTHORIZATION;
 GRANT UPDATE ON base_tbl TO regress_view_user1;
 SET SESSION AUTHORIZATION regress_view_user1;
@@ -1172,6 +1435,8 @@ SELECT * FROM rw_view1 FOR UPDATE;
 (1 row)
 
 UPDATE rw_view1 SET b = 'foo' WHERE a = 1;
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo';
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;
  a |  b  | c 
@@ -1183,6 +1448,9 @@ SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
 ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'bar'; -- not allowed
+ERROR:  permission denied for view rw_view1
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT UPDATE ON rw_view1 TO regress_view_user2;
 SET SESSION AUTHORIZATION regress_view_user2;
@@ -1199,30 +1467,38 @@ SELECT * FROM rw_view2 FOR UPDATE;
 (1 row)
 
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'fud';
 RESET SESSION AUTHORIZATION;
 REVOKE UPDATE ON base_tbl FROM regress_view_user1;
 SET SESSION AUTHORIZATION regress_view_user1;
 SELECT * FROM rw_view1;
  a |  b  | c 
 ---+-----+---
- 1 | bar | 1
+ 1 | fud | 1
 (1 row)
 
 SELECT * FROM rw_view1 FOR UPDATE;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view1 SET b = 'foo' WHERE a = 1;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;
  a |  b  | c 
 ---+-----+---
- 1 | bar | 1
+ 1 | fud | 1
 (1 row)
 
 SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
+ERROR:  permission denied for table base_tbl
 RESET SESSION AUTHORIZATION;
 DROP TABLE base_tbl CASCADE;
 NOTICE:  drop cascades to 2 other objects
@@ -1250,10 +1526,16 @@ UPDATE base_tbl SET a=a; -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view1 SET bb=bb, cc=cc; -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
+ERROR:  permission denied for table base_tbl
 DELETE FROM base_tbl; -- not allowed
 ERROR:  permission denied for table base_tbl
 DELETE FROM rw_view1; -- not allowed
 ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN DELETE; -- not allowed
+ERROR:  permission denied for view rw_view1
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT SELECT ON base_tbl TO regress_view_user2;
 GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2;
@@ -1276,10 +1558,18 @@ UPDATE base_tbl SET a=a, c=c; -- ok
 UPDATE base_tbl SET b=b; -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view1 SET cc=cc; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET cc = cc; -- ok
 UPDATE rw_view1 SET aa=aa; -- not allowed
 ERROR:  permission denied for view rw_view1
 UPDATE rw_view1 SET bb=bb; -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
 SET SESSION AUTHORIZATION regress_view_user2;
@@ -1289,6 +1579,9 @@ ERROR:  permission denied for view rw_view1
 DELETE FROM base_tbl WHERE a=1; -- ok
 DELETE FROM rw_view1 WHERE aa=2; -- not allowed
 ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN DELETE; -- not allowed
+ERROR:  permission denied for view rw_view1
 SET SESSION AUTHORIZATION regress_view_user1;
 REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2;
 GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2;
@@ -1297,17 +1590,21 @@ INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
 ERROR:  permission denied for table base_tbl
 DELETE FROM rw_view1 WHERE aa=2; -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN DELETE; -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
 SET SESSION AUTHORIZATION regress_view_user2;
 INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- ok
 DELETE FROM rw_view1 WHERE aa=2; -- ok
+MERGE INTO rw_view1 t USING (VALUES (3)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN DELETE; -- ok
 SELECT * FROM base_tbl; -- ok
  a |   b   | c 
 ---+-------+---
- 3 | Row 3 | 3
  4 | Row 4 | 4
-(2 rows)
+(1 row)
 
 RESET SESSION AUTHORIZATION;
 DROP TABLE base_tbl CASCADE;
@@ -1322,6 +1619,9 @@ SELECT * FROM rw_view1;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view1 SET aa=aa;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aa = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user2;
 CREATE VIEW rw_view2 AS SELECT cc AS ccc, aa AS aaa, bb AS bbb FROM rw_view1;
 GRANT SELECT, UPDATE ON rw_view2 TO regress_view_user3;
@@ -1329,6 +1629,9 @@ SELECT * FROM rw_view2;  -- not allowed
 ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aaa = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.c, v.a, v.b); -- not allowed
+ERROR:  permission denied for view rw_view1
 RESET SESSION AUTHORIZATION;
 GRANT SELECT ON base_tbl TO regress_view_user1;
 GRANT UPDATE (a, b) ON base_tbl TO regress_view_user1;
@@ -1342,16 +1645,27 @@ SELECT * FROM rw_view1; -- ok
 UPDATE rw_view1 SET aa=aa, bb=bb;  -- ok
 UPDATE rw_view1 SET cc=cc;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa, bb = bb; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET cc = cc; -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;  -- not allowed
 ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR:  permission denied for view rw_view1
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- not allowed
 ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR:  permission denied for view rw_view1
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT SELECT ON rw_view1 TO regress_view_user2;
 GRANT UPDATE (bb, cc) ON rw_view1 TO regress_view_user2;
@@ -1360,11 +1674,17 @@ SELECT * FROM rw_view2;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR:  permission denied for table base_tbl
 RESET SESSION AUTHORIZATION;
 GRANT SELECT ON base_tbl TO regress_view_user2;
 GRANT UPDATE (a, c) ON base_tbl TO regress_view_user2;
@@ -1380,6 +1700,14 @@ ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- not allowed
 ERROR:  permission denied for table base_tbl
@@ -1389,6 +1717,15 @@ UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET ccc=ccc;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
+ERROR:  permission denied for table base_tbl
 RESET SESSION AUTHORIZATION;
 GRANT SELECT ON base_tbl TO regress_view_user3;
 GRANT UPDATE (a, c) ON base_tbl TO regress_view_user3;
@@ -1404,6 +1741,14 @@ ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 RESET SESSION AUTHORIZATION;
 REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user1;
 SET SESSION AUTHORIZATION regress_view_user1;
@@ -1411,6 +1756,9 @@ SELECT * FROM rw_view1;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view1 SET aa=aa;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;  -- ok
  ccc | aaa |  bbb  
@@ -1423,6 +1771,14 @@ ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- ok
  ccc | aaa |  bbb  
@@ -1435,6 +1791,14 @@ ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 RESET SESSION AUTHORIZATION;
 REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user2;
 SET SESSION AUTHORIZATION regress_view_user2;
@@ -1446,6 +1810,15 @@ UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET ccc=ccc;  -- not allowed
 ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
+ERROR:  permission denied for table base_tbl
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- ok
  ccc | aaa |  bbb  
@@ -1458,6 +1831,14 @@ ERROR:  permission denied for view rw_view1
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 ERROR:  permission denied for table base_tbl
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+ERROR:  permission denied for view rw_view1
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+ERROR:  permission denied for table base_tbl
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 RESET SESSION AUTHORIZATION;
 DROP TABLE base_tbl CASCADE;
 NOTICE:  drop cascades to 2 other objects
@@ -1475,6 +1856,8 @@ CREATE VIEW rw_view1 AS SELECT a AS aa, b AS bb FROM base_tbl;
 ALTER VIEW rw_view1 ALTER COLUMN bb SET DEFAULT 'View default';
 INSERT INTO rw_view1 VALUES (4, 'Row 4');
 INSERT INTO rw_view1 (aa) VALUES (5);
+MERGE INTO rw_view1 t USING (VALUES (6)) AS v(a) ON t.aa = v.a
+  WHEN NOT MATCHED THEN INSERT (aa) VALUES (v.a);
 SELECT * FROM base_tbl;
  a |      b       | c 
 ---+--------------+---
@@ -1483,7 +1866,8 @@ SELECT * FROM base_tbl;
  3 | Unspecified  | 3
  4 | Row 4        | 4
  5 | View default | 5
-(5 rows)
+ 6 | View default | 6
+(6 rows)
 
 DROP TABLE base_tbl CASCADE;
 NOTICE:  drop cascades to view rw_view1
@@ -1857,6 +2241,38 @@ SELECT * FROM base_tbl_child ORDER BY a;
  20
 (6 rows)
 
+MERGE INTO rw_view1 t USING (VALUES (-200), (10)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -199 and 11
+MERGE INTO ONLY rw_view1 t USING (VALUES (-100), (20)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -99 and 21
+MERGE INTO rw_view2 t USING (VALUES (-40), (3)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -39 only
+MERGE INTO ONLY rw_view2 t USING (VALUES (-30), (4)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -29 only
+SELECT * FROM ONLY base_tbl_parent ORDER BY a;
+  a   
+------
+ -199
+  -99
+  -39
+  -29
+  -20
+  -10
+  100
+  200
+(8 rows)
+
+SELECT * FROM base_tbl_child ORDER BY a;
+ a  
+----
+  3
+  4
+  7
+  8
+ 11
+ 21
+(6 rows)
+
 CREATE TABLE other_tbl_parent (id int);
 CREATE TABLE other_tbl_child () INHERITS (other_tbl_parent);
 INSERT INTO other_tbl_parent VALUES (7),(200);
@@ -1886,10 +2302,10 @@ UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
 SELECT * FROM ONLY base_tbl_parent ORDER BY a;
   a   
 ------
- -200
- -100
-  -40
-  -30
+ -199
+  -99
+  -39
+  -29
   -20
   -10
  1100
@@ -1901,8 +2317,8 @@ SELECT * FROM base_tbl_child ORDER BY a;
 ------
     3
     4
-   10
-   20
+   11
+   21
  1007
  1008
 (6 rows)
@@ -1955,16 +2371,39 @@ INSERT INTO rw_view1(a) VALUES (9); -- ok
 INSERT INTO rw_view1(a) VALUES (10); -- should fail
 ERROR:  new row violates check option for view "rw_view1"
 DETAIL:  Failing row contains (10, 10).
-SELECT * FROM base_tbl;
+SELECT * FROM base_tbl ORDER BY a, b;
  a | b  
 ---+----
+ 1 | -1
  1 |  2
  2 |  3
- 1 | -1
  3 |  5
  9 | 10
 (5 rows)
 
+MERGE INTO rw_view1 t USING (VALUES (10)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a + 1); -- ok
+MERGE INTO rw_view1 t USING (VALUES (11)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a - 1); -- should fail
+ERROR:  new row violates check option for view "rw_view1"
+DETAIL:  Failing row contains (11, 10).
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a - 1; -- ok
+MERGE INTO rw_view1 t USING (VALUES (2)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a + 1; -- should fail
+ERROR:  new row violates check option for view "rw_view1"
+DETAIL:  Failing row contains (3, 3).
+SELECT * FROM base_tbl ORDER BY a, b;
+ a  | b  
+----+----
+  0 |  2
+  1 | -1
+  2 |  3
+  3 |  5
+  9 | 10
+ 10 | 11
+(6 rows)
+
 DROP TABLE base_tbl CASCADE;
 NOTICE:  drop cascades to view rw_view1
 -- WITH LOCAL/CASCADED CHECK OPTION
@@ -2228,17 +2667,31 @@ CREATE VIEW rw_view2 AS
 INSERT INTO rw_view2 VALUES (-5); -- should fail
 ERROR:  new row violates check option for view "rw_view2"
 DETAIL:  Failing row contains (-5).
+MERGE INTO rw_view2 t USING (VALUES (-5)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- should fail
+ERROR:  new row violates check option for view "rw_view2"
+DETAIL:  Failing row contains (-5).
 INSERT INTO rw_view2 VALUES (5); -- ok
+MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok
 INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+MERGE INTO rw_view2 t USING (VALUES (60)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok, but not in view
 UPDATE rw_view2 SET a = a - 10; -- should fail
 ERROR:  new row violates check option for view "rw_view2"
 DETAIL:  Failing row contains (-5).
+MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a - 10; -- should fail
+ERROR:  new row violates check option for view "rw_view2"
+DETAIL:  Failing row contains (-4).
 SELECT * FROM base_tbl;
  a  | b  
 ----+----
   5 | 10
+  6 | 10
  50 | 10
-(2 rows)
+ 60 | 10
+(4 rows)
 
 -- Check option won't cascade down to base view with INSTEAD OF triggers
 ALTER VIEW rw_view2 SET (check_option=cascaded);
@@ -2247,10 +2700,12 @@ UPDATE rw_view2 SET a = 200 WHERE a = 5; -- ok, but not in view (doesn't fail rw
 SELECT * FROM base_tbl;
   a  | b  
 -----+----
+   6 | 10
   50 | 10
+  60 | 10
  100 | 10
  200 | 10
-(3 rows)
+(5 rows)
 
 -- Neither local nor cascaded check options work with INSTEAD rules
 DROP TRIGGER rw_view1_trig ON rw_view1;
@@ -2267,14 +2722,16 @@ UPDATE rw_view2 SET a = -5 WHERE a = 5; -- ok, but not in view (doesn't fail rw_
 SELECT * FROM base_tbl;
   a  | b  
 -----+----
+   6 | 10
   50 | 10
+  60 | 10
  100 | 10
  200 | 10
  -10 | 10
   20 | 10
   30 | 10
   -5 | 10
-(7 rows)
+(9 rows)
 
 DROP TABLE base_tbl CASCADE;
 NOTICE:  drop cascades to 2 other objects
@@ -2375,6 +2832,11 @@ NOTICE:  snooped value: Harry
 DELETE FROM rw_view1 WHERE NOT snoop(person);
 NOTICE:  snooped value: Tom
 NOTICE:  snooped value: Harry
+MERGE INTO rw_view1 t
+  USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
+  WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
+NOTICE:  snooped value: Tom
+NOTICE:  snooped value: Harry
 EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person);
                   QUERY PLAN                   
 -----------------------------------------------
@@ -2400,6 +2862,21 @@ EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
          Filter: ((visibility = 'public'::text) AND (NOT snoop(person)))
 (3 rows)
 
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+  USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
+  WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Merge on base_tbl
+   ->  Nested Loop
+         Join Filter: (base_tbl.person = "*VALUES*".column1)
+         ->  Seq Scan on base_tbl
+               Filter: (visibility = 'public'::text)
+         ->  Materialize
+               ->  Values Scan on "*VALUES*"
+(7 rows)
+
 -- security barrier view on top of security barrier view
 CREATE VIEW rw_view2 WITH (security_barrier = true) AS
   SELECT * FROM rw_view1 WHERE snoop(person);
@@ -2449,6 +2926,13 @@ NOTICE:  snooped value: Tom
 NOTICE:  snooped value: Tom
 NOTICE:  snooped value: Harry
 NOTICE:  snooped value: Harry
+MERGE INTO rw_view2 t
+  USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
+  WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
+NOTICE:  snooped value: Tom
+NOTICE:  snooped value: Tom
+NOTICE:  snooped value: Harry
+NOTICE:  snooped value: Harry
 EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person);
                      QUERY PLAN                      
 -----------------------------------------------------
@@ -2476,6 +2960,20 @@ EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
          Filter: ((visibility = 'public'::text) AND snoop(person) AND (NOT snoop(person)))
 (3 rows)
 
+EXPLAIN (costs off)
+MERGE INTO rw_view2 t
+  USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
+  WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Merge on base_tbl
+   ->  Nested Loop
+         Join Filter: (base_tbl.person = "*VALUES*".column1)
+         ->  Seq Scan on base_tbl
+               Filter: ((visibility = 'public'::text) AND snoop(person))
+         ->  Values Scan on "*VALUES*"
+(6 rows)
+
 DROP TABLE base_tbl CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to view rw_view1
@@ -2941,6 +3439,29 @@ create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
 insert into uv_ptv_wco values (1, 2);
 ERROR:  new row violates check option for view "uv_ptv_wco"
 DETAIL:  Failing row contains (1, 2, null).
+merge into uv_ptv t
+  using (values (1,2), (1,4)) as v(a,b) on t.a = v.a -- fail: matches 2 src rows
+  when matched then update set b = t.b + 1
+  when not matched then insert values (v.a, v.b + 1);
+ERROR:  MERGE command cannot affect row a second time
+HINT:  Ensure that not more than one source row matches any one target row.
+merge into uv_ptv t
+  using (values (1,2), (1,4)) as v(a,b) on t.a = v.a and t.b = v.b
+  when matched then update set b = t.b + 1
+  when not matched then insert values (v.a, v.b + 1); -- fail: no partition for b=5
+ERROR:  no partition of relation "uv_pt1" found for row
+DETAIL:  Partition key of the failing row contains (b) = (5).
+merge into uv_ptv t
+  using (values (1,2), (1,3)) as v(a,b) on t.a = v.a and t.b = v.b
+  when matched then update set b = t.b + 1
+  when not matched then insert values (v.a, v.b + 1); -- ok
+select tableoid::regclass, * from uv_pt order by a, b;
+ tableoid | a | b | v 
+----------+---+---+---
+ uv_pt11  | 1 | 3 | 
+ uv_pt11  | 1 | 4 | 
+(2 rows)
+
 drop view uv_ptv, uv_ptv_wco;
 drop table uv_pt, uv_pt1, uv_pt11;
 -- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions
index 82faa7364ca9210fd6a502dc2d6ba665c0c944d4..79a18684e481c3225e4339f45f5e467748404ace 100644 (file)
@@ -100,15 +100,6 @@ COPY (
 ) TO stdout;
 
 -- unsupported relation types
--- view
-CREATE VIEW tv AS SELECT * FROM target;
-MERGE INTO tv t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
-       INSERT DEFAULT VALUES;
-DROP VIEW tv;
-
 -- materialized view
 CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
 MERGE INTO mv t
index 93aff4e66fff5e8b8a52b69b84dee66ea9f0244c..6924012a10c973b0eaacf1d571d5b33f48891906 100644 (file)
@@ -1277,6 +1277,19 @@ MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
        WHEN NOT MATCHED THEN
                INSERT VALUES (s.a, '');
 
+-- also ok if the rules are disabled
+ALTER TABLE rule_merge1 DISABLE RULE rule1;
+ALTER TABLE rule_merge1 DISABLE RULE rule2;
+ALTER TABLE rule_merge1 DISABLE RULE rule3;
+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, '');
+
 -- test deparsing
 CREATE TABLE sf_target(id int, data text, filling int[]);
 
index eaee0b7e1d7164be271e5c7032211a5a0d2062c0..ae11e46da2813ac753851972aadc6940fef24269 100644 (file)
@@ -62,6 +62,15 @@ UPDATE ro_view10 SET a=a+1;
 UPDATE ro_view11 SET a=a+1;
 UPDATE ro_view12 SET a=a+1;
 INSERT INTO ro_view13 VALUES (3, 'Row 3');
+MERGE INTO ro_view13 AS t USING (VALUES (1, 'Row 1')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN DELETE;
+MERGE INTO ro_view13 AS t USING (VALUES (2, 'Row 2')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = v.b;
+MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b);
+MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN DO NOTHING
+  WHEN NOT MATCHED THEN DO NOTHING; -- should be OK to do nothing
 -- Partially updatable view
 INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
 INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
@@ -69,6 +78,20 @@ UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
 UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
 SELECT * FROM base_tbl;
 DELETE FROM rw_view14 WHERE a=3; -- should be OK
+MERGE INTO rw_view14  AS t
+  USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = v.b  -- should be OK, except...
+  WHEN NOT MATCHED THEN INSERT VALUES (null, v.a, v.b); -- should fail
+MERGE INTO rw_view14  AS t
+  USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = v.b  -- should be OK
+  WHEN NOT MATCHED THEN INSERT (a,b) VALUES (v.a, v.b); -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
+MERGE INTO rw_view14  AS t
+  USING (VALUES (2, 'Row 2'), (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED AND t.a = 2 THEN UPDATE SET b = v.b  -- should be OK
+  WHEN MATCHED AND t.a = 3 THEN DELETE; -- should be OK
+SELECT * FROM base_tbl ORDER BY a;
 -- Partially updatable view
 INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
 INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
@@ -114,6 +137,8 @@ CREATE RULE rw_view16_del_rule AS ON DELETE TO rw_view16
 INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should fail
 UPDATE rw_view16 SET b='ROW 2' WHERE a=2; -- should fail
 DELETE FROM rw_view16 WHERE a=2; -- should fail
+MERGE INTO rw_view16 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
 
 DROP TABLE base_tbl CASCADE;
 DROP VIEW ro_view10, ro_view12, ro_view18;
@@ -145,9 +170,31 @@ UPDATE rw_view1 SET a=5 WHERE a=4;
 DELETE FROM rw_view1 WHERE b='Row 2';
 SELECT * FROM base_tbl;
 
+MERGE INTO rw_view1 t
+  USING (VALUES (0, 'ROW 0'), (1, 'ROW 1'),
+                (2, 'ROW 2'), (3, 'ROW 3')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED AND t.a <= 1 THEN UPDATE SET b = v.b
+  WHEN MATCHED THEN DELETE
+  WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a);
+SELECT * FROM base_tbl ORDER BY a;
+
 EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
 EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5;
 
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t USING (VALUES (5, 'X')) AS v(a,b) ON t.a = v.a
+  WHEN MATCHED THEN DELETE;
+
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+  USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = 'Updated';
+
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+  USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
+  WHEN NOT MATCHED THEN INSERT (a) VALUES (s.a);
+
 -- it's still updatable if we add a DO ALSO rule
 
 CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
@@ -195,6 +242,13 @@ UPDATE rw_view2 SET bbb='Row 4' WHERE aaa=4;
 DELETE FROM rw_view2 WHERE aaa=2;
 SELECT * FROM rw_view2;
 
+MERGE INTO rw_view2 t
+  USING (VALUES (3, 'R3'), (4, 'R4'), (5, 'R5')) AS v(a,b) ON aaa = v.a
+  WHEN MATCHED AND aaa = 3 THEN DELETE
+  WHEN MATCHED THEN UPDATE SET bbb = v.b
+  WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a);
+SELECT * FROM rw_view2 ORDER BY aaa;
+
 EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
 EXPLAIN (costs off) DELETE FROM rw_view2 WHERE aaa=4;
 
@@ -283,6 +337,9 @@ SELECT * FROM rw_view2;
 DELETE FROM rw_view2 WHERE a=3 RETURNING *;
 SELECT * FROM rw_view2;
 
+MERGE INTO rw_view2 t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
+
 EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
 EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
 
@@ -397,9 +454,56 @@ SELECT * FROM rw_view2;
 DELETE FROM rw_view2 WHERE a=3 RETURNING *;
 SELECT * FROM rw_view2;
 
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED AND t.a <= 1 THEN DELETE
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+SELECT * FROM base_tbl ORDER BY a;
+
 EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
 EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
 
+EXPLAIN (costs off)
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED AND t.a <= 1 THEN DELETE
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+
+-- MERGE with incomplete set of INSTEAD OF triggers
+DROP TRIGGER rw_view1_del_trig ON rw_view1;
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED AND t.a <= 1 THEN DELETE
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- ok
+
+DROP TRIGGER rw_view1_ins_trig ON rw_view1;
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
+
+-- MERGE with INSTEAD OF triggers on auto-updatable view
+CREATE TRIGGER rw_view2_upd_trig INSTEAD OF UPDATE ON rw_view2
+  FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b
+  WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
+MERGE INTO rw_view2 t
+  USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
+  WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
+SELECT * FROM base_tbl ORDER BY a;
+
 DROP TABLE base_tbl CASCADE;
 DROP FUNCTION rw_view1_trig_fn();
 
@@ -451,6 +555,13 @@ INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed
 INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
 INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
 
+MERGE INTO rw_view1 t
+  USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+MERGE INTO rw_view2 t
+  USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
+
 UPDATE base_tbl SET a=a, c=c; -- ok
 UPDATE base_tbl SET b=b; -- not allowed
 UPDATE rw_view1 SET bb=bb, cc=cc; -- ok
@@ -458,6 +569,15 @@ UPDATE rw_view1 SET aa=aa; -- not allowed
 UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
 UPDATE rw_view2 SET bb=bb; -- not allowed
 
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET bb = bb, cc = cc; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa, cc = cc; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
+
 DELETE FROM base_tbl; -- not allowed
 DELETE FROM rw_view1; -- not allowed
 DELETE FROM rw_view2; -- not allowed
@@ -474,6 +594,10 @@ INSERT INTO rw_view2 VALUES ('Row 4', 4.0, 4); -- ok
 DELETE FROM base_tbl WHERE a=1; -- ok
 DELETE FROM rw_view1 WHERE aa=2; -- not allowed
 DELETE FROM rw_view2 WHERE aa=2; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
 SELECT * FROM base_tbl;
 RESET SESSION AUTHORIZATION;
 
@@ -489,6 +613,10 @@ INSERT INTO rw_view2 VALUES ('Row 6', 6.0, 6); -- not allowed
 DELETE FROM base_tbl WHERE a=3; -- not allowed
 DELETE FROM rw_view1 WHERE aa=3; -- ok
 DELETE FROM rw_view2 WHERE aa=4; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
 SELECT * FROM base_tbl;
 RESET SESSION AUTHORIZATION;
 
@@ -504,12 +632,16 @@ CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
 SELECT * FROM rw_view1;  -- not allowed
 SELECT * FROM rw_view1 FOR UPDATE;  -- not allowed
 UPDATE rw_view1 SET b = 'foo' WHERE a = 1;  -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user2;
 CREATE VIEW rw_view2 AS SELECT * FROM rw_view1;
 SELECT * FROM rw_view2;  -- not allowed
 SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
 
 RESET SESSION AUTHORIZATION;
 GRANT SELECT ON base_tbl TO regress_view_user1;
@@ -518,11 +650,15 @@ SET SESSION AUTHORIZATION regress_view_user1;
 SELECT * FROM rw_view1;
 SELECT * FROM rw_view1 FOR UPDATE;  -- not allowed
 UPDATE rw_view1 SET b = 'foo' WHERE a = 1;  -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;  -- not allowed
 SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT SELECT ON rw_view1 TO regress_view_user2;
@@ -531,6 +667,8 @@ SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;
 SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
 
 RESET SESSION AUTHORIZATION;
 GRANT UPDATE ON base_tbl TO regress_view_user1;
@@ -539,11 +677,15 @@ SET SESSION AUTHORIZATION regress_view_user1;
 SELECT * FROM rw_view1;
 SELECT * FROM rw_view1 FOR UPDATE;
 UPDATE rw_view1 SET b = 'foo' WHERE a = 1;
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo';
 
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;
 SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'bar'; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT UPDATE ON rw_view1 TO regress_view_user2;
@@ -552,6 +694,8 @@ SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;
 SELECT * FROM rw_view2 FOR UPDATE;
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'fud';
 
 RESET SESSION AUTHORIZATION;
 REVOKE UPDATE ON base_tbl FROM regress_view_user1;
@@ -560,11 +704,15 @@ SET SESSION AUTHORIZATION regress_view_user1;
 SELECT * FROM rw_view1;
 SELECT * FROM rw_view1 FOR UPDATE;  -- not allowed
 UPDATE rw_view1 SET b = 'foo' WHERE a = 1;  -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;
 SELECT * FROM rw_view2 FOR UPDATE;  -- not allowed
 UPDATE rw_view2 SET b = 'bar' WHERE a = 1;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
 
 RESET SESSION AUTHORIZATION;
 
@@ -588,8 +736,12 @@ INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed
 INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
 UPDATE base_tbl SET a=a; -- not allowed
 UPDATE rw_view1 SET bb=bb, cc=cc; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
 DELETE FROM base_tbl; -- not allowed
 DELETE FROM rw_view1; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN DELETE; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT SELECT ON base_tbl TO regress_view_user2;
@@ -601,8 +753,14 @@ SELECT * FROM rw_view1; -- ok
 UPDATE base_tbl SET a=a, c=c; -- ok
 UPDATE base_tbl SET b=b; -- not allowed
 UPDATE rw_view1 SET cc=cc; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET cc = cc; -- ok
 UPDATE rw_view1 SET aa=aa; -- not allowed
 UPDATE rw_view1 SET bb=bb; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
@@ -612,6 +770,8 @@ INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- ok
 INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
 DELETE FROM base_tbl WHERE a=1; -- ok
 DELETE FROM rw_view1 WHERE aa=2; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN DELETE; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user1;
 REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2;
@@ -620,6 +780,8 @@ GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2;
 SET SESSION AUTHORIZATION regress_view_user2;
 INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
 DELETE FROM rw_view1 WHERE aa=2; -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN DELETE; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
@@ -627,6 +789,8 @@ GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
 SET SESSION AUTHORIZATION regress_view_user2;
 INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- ok
 DELETE FROM rw_view1 WHERE aa=2; -- ok
+MERGE INTO rw_view1 t USING (VALUES (3)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN DELETE; -- ok
 SELECT * FROM base_tbl; -- ok
 
 RESET SESSION AUTHORIZATION;
@@ -643,12 +807,16 @@ CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
 ALTER VIEW rw_view1 SET (security_invoker = true);
 SELECT * FROM rw_view1;  -- not allowed
 UPDATE rw_view1 SET aa=aa;  -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aa = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user2;
 CREATE VIEW rw_view2 AS SELECT cc AS ccc, aa AS aaa, bb AS bbb FROM rw_view1;
 GRANT SELECT, UPDATE ON rw_view2 TO regress_view_user3;
 SELECT * FROM rw_view2;  -- not allowed
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aaa = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.c, v.a, v.b); -- not allowed
 
 RESET SESSION AUTHORIZATION;
 
@@ -659,14 +827,22 @@ SET SESSION AUTHORIZATION regress_view_user1;
 SELECT * FROM rw_view1; -- ok
 UPDATE rw_view1 SET aa=aa, bb=bb;  -- ok
 UPDATE rw_view1 SET cc=cc;  -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa, bb = bb; -- ok
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET cc = cc; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;  -- not allowed
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- not allowed
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user1;
 GRANT SELECT ON rw_view1 TO regress_view_user2;
@@ -675,10 +851,14 @@ GRANT UPDATE (bb, cc) ON rw_view1 TO regress_view_user2;
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;  -- not allowed
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- not allowed
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
 
 RESET SESSION AUTHORIZATION;
 
@@ -690,12 +870,24 @@ SELECT * FROM rw_view2;  -- ok
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- not allowed
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 UPDATE rw_view2 SET ccc=ccc;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
 
 RESET SESSION AUTHORIZATION;
 
@@ -707,6 +899,12 @@ SELECT * FROM rw_view2;  -- ok
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 
 RESET SESSION AUTHORIZATION;
 
@@ -715,18 +913,32 @@ REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user1;
 SET SESSION AUTHORIZATION regress_view_user1;
 SELECT * FROM rw_view1;  -- not allowed
 UPDATE rw_view1 SET aa=aa;  -- not allowed
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
+  WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user2;
 SELECT * FROM rw_view2;  -- ok
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- ok
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 
 RESET SESSION AUTHORIZATION;
 
@@ -737,12 +949,24 @@ SELECT * FROM rw_view2;  -- not allowed
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 UPDATE rw_view2 SET ccc=ccc;  -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
 
 SET SESSION AUTHORIZATION regress_view_user3;
 SELECT * FROM rw_view2;  -- ok
 UPDATE rw_view2 SET aaa=aaa;  -- not allowed
 UPDATE rw_view2 SET bbb=bbb;  -- not allowed
 UPDATE rw_view2 SET ccc=ccc;  -- ok
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
+MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
+  WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
 
 RESET SESSION AUTHORIZATION;
 
@@ -764,6 +988,8 @@ ALTER VIEW rw_view1 ALTER COLUMN bb SET DEFAULT 'View default';
 
 INSERT INTO rw_view1 VALUES (4, 'Row 4');
 INSERT INTO rw_view1 (aa) VALUES (5);
+MERGE INTO rw_view1 t USING (VALUES (6)) AS v(a) ON t.aa = v.a
+  WHEN NOT MATCHED THEN INSERT (aa) VALUES (v.a);
 
 SELECT * FROM base_tbl;
 
@@ -945,6 +1171,18 @@ DELETE FROM ONLY rw_view2 WHERE a IN (-8, 8); -- Should delete -8 only
 SELECT * FROM ONLY base_tbl_parent ORDER BY a;
 SELECT * FROM base_tbl_child ORDER BY a;
 
+MERGE INTO rw_view1 t USING (VALUES (-200), (10)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -199 and 11
+MERGE INTO ONLY rw_view1 t USING (VALUES (-100), (20)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -99 and 21
+MERGE INTO rw_view2 t USING (VALUES (-40), (3)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -39 only
+MERGE INTO ONLY rw_view2 t USING (VALUES (-30), (4)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -29 only
+
+SELECT * FROM ONLY base_tbl_parent ORDER BY a;
+SELECT * FROM base_tbl_child ORDER BY a;
+
 CREATE TABLE other_tbl_parent (id int);
 CREATE TABLE other_tbl_child () INHERITS (other_tbl_parent);
 INSERT INTO other_tbl_parent VALUES (7),(200);
@@ -977,7 +1215,17 @@ UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
 UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
 INSERT INTO rw_view1(a) VALUES (9); -- ok
 INSERT INTO rw_view1(a) VALUES (10); -- should fail
-SELECT * FROM base_tbl;
+SELECT * FROM base_tbl ORDER BY a, b;
+
+MERGE INTO rw_view1 t USING (VALUES (10)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a + 1); -- ok
+MERGE INTO rw_view1 t USING (VALUES (11)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a - 1); -- should fail
+MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a - 1; -- ok
+MERGE INTO rw_view1 t USING (VALUES (2)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a + 1; -- should fail
+SELECT * FROM base_tbl ORDER BY a, b;
 
 DROP TABLE base_tbl CASCADE;
 
@@ -1139,9 +1387,17 @@ CREATE VIEW rw_view2 AS
   SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
+MERGE INTO rw_view2 t USING (VALUES (-5)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- should fail
 INSERT INTO rw_view2 VALUES (5); -- ok
+MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok
 INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
+MERGE INTO rw_view2 t USING (VALUES (60)) AS v(a) ON t.a = v.a
+  WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok, but not in view
 UPDATE rw_view2 SET a = a - 10; -- should fail
+MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a
+  WHEN MATCHED THEN UPDATE SET a = t.a - 10; -- should fail
 SELECT * FROM base_tbl;
 
 -- Check option won't cascade down to base view with INSTEAD OF triggers
@@ -1229,10 +1485,17 @@ SELECT table_name, column_name, is_updatable
 SELECT * FROM rw_view1 WHERE snoop(person);
 UPDATE rw_view1 SET person=person WHERE snoop(person);
 DELETE FROM rw_view1 WHERE NOT snoop(person);
+MERGE INTO rw_view1 t
+  USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
+  WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
 
 EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person);
 EXPLAIN (costs off) UPDATE rw_view1 SET person=person WHERE snoop(person);
 EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
+EXPLAIN (costs off)
+MERGE INTO rw_view1 t
+  USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
+  WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
 
 -- security barrier view on top of security barrier view
 
@@ -1255,10 +1518,17 @@ SELECT table_name, column_name, is_updatable
 SELECT * FROM rw_view2 WHERE snoop(person);
 UPDATE rw_view2 SET person=person WHERE snoop(person);
 DELETE FROM rw_view2 WHERE NOT snoop(person);
+MERGE INTO rw_view2 t
+  USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
+  WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
 
 EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person);
 EXPLAIN (costs off) UPDATE rw_view2 SET person=person WHERE snoop(person);
 EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
+EXPLAIN (costs off)
+MERGE INTO rw_view2 t
+  USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
+  WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
 
 DROP TABLE base_tbl CASCADE;
 
@@ -1480,6 +1750,19 @@ insert into uv_ptv values (1, 2);
 select tableoid::regclass, * from uv_pt;
 create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
 insert into uv_ptv_wco values (1, 2);
+merge into uv_ptv t
+  using (values (1,2), (1,4)) as v(a,b) on t.a = v.a -- fail: matches 2 src rows
+  when matched then update set b = t.b + 1
+  when not matched then insert values (v.a, v.b + 1);
+merge into uv_ptv t
+  using (values (1,2), (1,4)) as v(a,b) on t.a = v.a and t.b = v.b
+  when matched then update set b = t.b + 1
+  when not matched then insert values (v.a, v.b + 1); -- fail: no partition for b=5
+merge into uv_ptv t
+  using (values (1,2), (1,3)) as v(a,b) on t.a = v.a and t.b = v.b
+  when matched then update set b = t.b + 1
+  when not matched then insert values (v.a, v.b + 1); -- ok
+select tableoid::regclass, * from uv_pt order by a, b;
 drop view uv_ptv, uv_ptv_wco;
 drop table uv_pt, uv_pt1, uv_pt11;