]> granicus.if.org Git - postgresql/commitdiff
Modified files for MERGE
authorSimon Riggs <simon@2ndQuadrant.com>
Mon, 2 Apr 2018 20:12:47 +0000 (21:12 +0100)
committerSimon Riggs <simon@2ndQuadrant.com>
Mon, 2 Apr 2018 20:12:47 +0000 (21:12 +0100)
82 files changed:
contrib/test_decoding/expected/ddl.out
contrib/test_decoding/sql/ddl.sql
doc/src/sgml/libpq.sgml
doc/src/sgml/mvcc.sgml
doc/src/sgml/plpgsql.sgml
doc/src/sgml/ref/allfiles.sgml
doc/src/sgml/ref/create_policy.sgml
doc/src/sgml/ref/insert.sgml
doc/src/sgml/reference.sgml
doc/src/sgml/trigger.sgml
src/backend/access/heap/heapam.c
src/backend/catalog/sql_features.txt
src/backend/commands/explain.c
src/backend/commands/prepare.c
src/backend/commands/trigger.c
src/backend/executor/Makefile
src/backend/executor/README
src/backend/executor/execMain.c
src/backend/executor/execPartition.c
src/backend/executor/execReplication.c
src/backend/executor/nodeModifyTable.c
src/backend/executor/spi.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/prep/preptlist.c
src/backend/optimizer/util/pathnode.c
src/backend/optimizer/util/plancat.c
src/backend/parser/Makefile
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_agg.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_collate.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_func.c
src/backend/parser/parse_relation.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rowsecurity.c
src/backend/tcop/pquery.c
src/backend/tcop/utility.c
src/include/access/heapam.h
src/include/commands/trigger.h
src/include/executor/execPartition.h
src/include/executor/instrument.h
src/include/executor/nodeModifyTable.h
src/include/executor/spi.h
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/nodes/relation.h
src/include/optimizer/pathnode.h
src/include/parser/analyze.h
src/include/parser/kwlist.h
src/include/parser/parse_clause.h
src/include/parser/parse_node.h
src/include/rewrite/rewriteHandler.h
src/interfaces/libpq/fe-exec.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_gram.y
src/pl/plpgsql/src/pl_scanner.c
src/pl/plpgsql/src/plpgsql.h
src/test/isolation/isolation_schedule
src/test/regress/expected/identity.out
src/test/regress/expected/privileges.out
src/test/regress/expected/rowsecurity.out
src/test/regress/expected/rules.out
src/test/regress/expected/triggers.out
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/identity.sql
src/test/regress/sql/privileges.sql
src/test/regress/sql/rowsecurity.sql
src/test/regress/sql/rules.sql
src/test/regress/sql/triggers.sql
src/tools/pgindent/typedefs.list

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