aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/test_decoding/expected/ddl.out46
-rw-r--r--contrib/test_decoding/sql/ddl.sql16
-rw-r--r--doc/src/sgml/libpq.sgml8
-rw-r--r--doc/src/sgml/mvcc.sgml28
-rw-r--r--doc/src/sgml/plpgsql.sgml3
-rw-r--r--doc/src/sgml/ref/allfiles.sgml1
-rw-r--r--doc/src/sgml/ref/create_policy.sgml7
-rw-r--r--doc/src/sgml/ref/insert.sgml11
-rw-r--r--doc/src/sgml/ref/merge.sgml617
-rw-r--r--doc/src/sgml/reference.sgml1
-rw-r--r--doc/src/sgml/trigger.sgml20
-rw-r--r--src/backend/access/heap/heapam.c28
-rw-r--r--src/backend/catalog/sql_features.txt6
-rw-r--r--src/backend/commands/explain.c33
-rw-r--r--src/backend/commands/prepare.c1
-rw-r--r--src/backend/commands/trigger.c270
-rw-r--r--src/backend/executor/Makefile2
-rw-r--r--src/backend/executor/README11
-rw-r--r--src/backend/executor/execMain.c17
-rw-r--r--src/backend/executor/execMerge.c682
-rw-r--r--src/backend/executor/execPartition.c121
-rw-r--r--src/backend/executor/execReplication.c4
-rw-r--r--src/backend/executor/nodeModifyTable.c280
-rw-r--r--src/backend/executor/spi.c3
-rw-r--r--src/backend/nodes/copyfuncs.c58
-rw-r--r--src/backend/nodes/equalfuncs.c49
-rw-r--r--src/backend/nodes/nodeFuncs.c64
-rw-r--r--src/backend/nodes/outfuncs.c40
-rw-r--r--src/backend/nodes/readfuncs.c45
-rw-r--r--src/backend/optimizer/plan/createplan.c22
-rw-r--r--src/backend/optimizer/plan/planner.c29
-rw-r--r--src/backend/optimizer/plan/setrefs.c54
-rw-r--r--src/backend/optimizer/prep/preptlist.c41
-rw-r--r--src/backend/optimizer/util/pathnode.c11
-rw-r--r--src/backend/optimizer/util/plancat.c4
-rw-r--r--src/backend/parser/Makefile2
-rw-r--r--src/backend/parser/analyze.c18
-rw-r--r--src/backend/parser/gram.y151
-rw-r--r--src/backend/parser/parse_agg.c10
-rw-r--r--src/backend/parser/parse_clause.c57
-rw-r--r--src/backend/parser/parse_collate.c1
-rw-r--r--src/backend/parser/parse_expr.c3
-rw-r--r--src/backend/parser/parse_func.c3
-rw-r--r--src/backend/parser/parse_merge.c654
-rw-r--r--src/backend/parser/parse_relation.c10
-rw-r--r--src/backend/rewrite/rewriteHandler.c115
-rw-r--r--src/backend/rewrite/rowsecurity.c97
-rw-r--r--src/backend/tcop/pquery.c5
-rw-r--r--src/backend/tcop/utility.c16
-rw-r--r--src/bin/psql/tab-complete.c87
-rw-r--r--src/include/access/heapam.h13
-rw-r--r--src/include/commands/trigger.h6
-rw-r--r--src/include/executor/execMerge.h31
-rw-r--r--src/include/executor/execPartition.h1
-rw-r--r--src/include/executor/instrument.h7
-rw-r--r--src/include/executor/nodeModifyTable.h23
-rw-r--r--src/include/executor/spi.h1
-rw-r--r--src/include/nodes/execnodes.h65
-rw-r--r--src/include/nodes/nodes.h7
-rw-r--r--src/include/nodes/parsenodes.h53
-rw-r--r--src/include/nodes/plannodes.h8
-rw-r--r--src/include/nodes/relation.h7
-rw-r--r--src/include/optimizer/pathnode.h7
-rw-r--r--src/include/parser/analyze.h5
-rw-r--r--src/include/parser/kwlist.h2
-rw-r--r--src/include/parser/parse_clause.h5
-rw-r--r--src/include/parser/parse_merge.h19
-rw-r--r--src/include/parser/parse_node.h5
-rw-r--r--src/include/rewrite/rewriteHandler.h1
-rw-r--r--src/interfaces/libpq/fe-exec.c9
-rw-r--r--src/pl/plpgsql/src/pl_exec.c5
-rw-r--r--src/pl/plpgsql/src/pl_gram.y8
-rw-r--r--src/pl/plpgsql/src/pl_scanner.c1
-rw-r--r--src/pl/plpgsql/src/plpgsql.h4
-rw-r--r--src/test/isolation/expected/merge-delete.out97
-rw-r--r--src/test/isolation/expected/merge-insert-update.out84
-rw-r--r--src/test/isolation/expected/merge-match-recheck.out106
-rw-r--r--src/test/isolation/expected/merge-update.out238
-rw-r--r--src/test/isolation/isolation_schedule4
-rw-r--r--src/test/isolation/specs/merge-delete.spec51
-rw-r--r--src/test/isolation/specs/merge-insert-update.spec52
-rw-r--r--src/test/isolation/specs/merge-match-recheck.spec79
-rw-r--r--src/test/isolation/specs/merge-update.spec133
-rw-r--r--src/test/regress/expected/identity.out55
-rw-r--r--src/test/regress/expected/merge.out1672
-rw-r--r--src/test/regress/expected/privileges.out98
-rw-r--r--src/test/regress/expected/rowsecurity.out182
-rw-r--r--src/test/regress/expected/rules.out31
-rw-r--r--src/test/regress/expected/triggers.out48
-rw-r--r--src/test/regress/expected/with.out137
-rw-r--r--src/test/regress/parallel_schedule2
-rw-r--r--src/test/regress/serial_schedule1
-rw-r--r--src/test/regress/sql/identity.sql45
-rw-r--r--src/test/regress/sql/merge.sql1173
-rw-r--r--src/test/regress/sql/privileges.sql108
-rw-r--r--src/test/regress/sql/rowsecurity.sql156
-rw-r--r--src/test/regress/sql/rules.sql33
-rw-r--r--src/test/regress/sql/triggers.sql47
-rw-r--r--src/test/regress/sql/with.sql56
-rw-r--r--src/tools/pgindent/typedefs.list3
100 files changed, 217 insertions, 8629 deletions
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 79c359d6e3d..b7c76469fc3 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -192,52 +192,6 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
COMMIT
(33 rows)
--- MERGE support
-BEGIN;
-MERGE INTO replication_example t
- USING (SELECT i as id, i as data, i as num FROM generate_series(-20, 5) i) s
- ON t.id = s.id
- WHEN MATCHED AND t.id < 0 THEN
- UPDATE SET somenum = somenum + 1
- WHEN MATCHED AND t.id >= 0 THEN
- DELETE
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.*);
-COMMIT;
-/* display results */
-SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
- data
---------------------------------------------------------------------------------------------------------------------------------------------------
- BEGIN
- table public.replication_example: INSERT: id[integer]:-20 somedata[integer]:-20 somenum[integer]:-20 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: INSERT: id[integer]:-19 somedata[integer]:-19 somenum[integer]:-19 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: INSERT: id[integer]:-18 somedata[integer]:-18 somenum[integer]:-18 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: INSERT: id[integer]:-17 somedata[integer]:-17 somenum[integer]:-17 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: INSERT: id[integer]:-16 somedata[integer]:-16 somenum[integer]:-16 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-15 somedata[integer]:-15 somenum[integer]:-14 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-14 somedata[integer]:-14 somenum[integer]:-13 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-13 somedata[integer]:-13 somenum[integer]:-12 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-12 somedata[integer]:-12 somenum[integer]:-11 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-11 somedata[integer]:-11 somenum[integer]:-10 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-10 somedata[integer]:-10 somenum[integer]:-9 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-9 somedata[integer]:-9 somenum[integer]:-8 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-8 somedata[integer]:-8 somenum[integer]:-7 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-7 somedata[integer]:-7 somenum[integer]:-6 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-6 somedata[integer]:-6 somenum[integer]:-5 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-5 somedata[integer]:-5 somenum[integer]:-4 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-4 somedata[integer]:-4 somenum[integer]:-3 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-3 somedata[integer]:-3 somenum[integer]:-2 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-2 somedata[integer]:-2 somenum[integer]:-1 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: UPDATE: id[integer]:-1 somedata[integer]:-1 somenum[integer]:0 zaphod1[integer]:null zaphod2[integer]:null
- table public.replication_example: DELETE: id[integer]:0
- table public.replication_example: DELETE: id[integer]:1
- table public.replication_example: DELETE: id[integer]:2
- table public.replication_example: DELETE: id[integer]:3
- table public.replication_example: DELETE: id[integer]:4
- table public.replication_example: DELETE: id[integer]:5
- COMMIT
-(28 rows)
-
CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
INSERT INTO tr_unique(data) VALUES(10);
ALTER TABLE tr_unique RENAME TO tr_pkey;
diff --git a/contrib/test_decoding/sql/ddl.sql b/contrib/test_decoding/sql/ddl.sql
index 0e608b252fa..c4b10a4cf9e 100644
--- a/contrib/test_decoding/sql/ddl.sql
+++ b/contrib/test_decoding/sql/ddl.sql
@@ -93,22 +93,6 @@ COMMIT;
/* display results */
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
--- MERGE support
-BEGIN;
-MERGE INTO replication_example t
- USING (SELECT i as id, i as data, i as num FROM generate_series(-20, 5) i) s
- ON t.id = s.id
- WHEN MATCHED AND t.id < 0 THEN
- UPDATE SET somenum = somenum + 1
- WHEN MATCHED AND t.id >= 0 THEN
- DELETE
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.*);
-COMMIT;
-
-/* display results */
-SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
-
CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int);
INSERT INTO tr_unique(data) VALUES(10);
ALTER TABLE tr_unique RENAME TO tr_pkey;
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 1626999a701..800e68a19e0 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -3917,11 +3917,9 @@ char *PQcmdTuples(PGresult *res);
<structname>PGresult</structname>. This function can only be used following
the execution of a <command>SELECT</command>, <command>CREATE TABLE AS</command>,
<command>INSERT</command>, <command>UPDATE</command>, <command>DELETE</command>,
- <command>MERGE</command>, <command>MOVE</command>, <command>FETCH</command>,
- or <command>COPY</command> statement, or an <command>EXECUTE</command> of a
- prepared query that contains an <command>INSERT</command>,
- <command>UPDATE</command>, <command>DELETE</command>
- or <command>MERGE</command> statement.
+ <command>MOVE</command>, <command>FETCH</command>, or <command>COPY</command> statement,
+ or an <command>EXECUTE</command> of a prepared query that contains an
+ <command>INSERT</command>, <command>UPDATE</command>, or <command>DELETE</command> statement.
If the command that generated the <structname>PGresult</structname> was anything
else, <function>PQcmdTuples</function> returns an empty string. The caller
should not free the return value directly. It will be freed when
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 0e3e89af560..24613e3c754 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -423,31 +423,6 @@ COMMIT;
</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,
subsequent commands in the same transaction will see the effects
@@ -925,8 +900,7 @@ ERROR: could not serialize access due to read/write dependencies among transact
<para>
The commands <command>UPDATE</command>,
- <command>DELETE</command>, <command>INSERT</command> and
- <command>MERGE</command>
+ <command>DELETE</command>, and <command>INSERT</command>
acquire this lock mode on the target table (in addition to
<literal>ACCESS SHARE</literal> locks on any other referenced
tables). In general, this lock mode will be acquired by any
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 59f6112b07c..5b2aac618e3 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -1246,7 +1246,7 @@ EXECUTE format('SELECT count(*) FROM %I '
</programlisting>
Another restriction on parameter symbols is that they only work in
<command>SELECT</command>, <command>INSERT</command>, <command>UPDATE</command>, and
- <command>DELETE</command> and <command>MERGE</command> commands. In other statement
+ <command>DELETE</command> commands. In other statement
types (generically called utility statements), you must insert
values textually even if they are just data values.
</para>
@@ -1529,7 +1529,6 @@ GET DIAGNOSTICS integer_var = ROW_COUNT;
<listitem>
<para>
<command>UPDATE</command>, <command>INSERT</command>, and <command>DELETE</command>
- and <command>MERGE</command>
statements set <literal>FOUND</literal> true if at least one
row is affected, false if no row is affected.
</para>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 7cd6ee85dc9..c81c87ef41c 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -159,7 +159,6 @@ Complete list of usable sgml source files in this directory.
<!ENTITY load SYSTEM "load.sgml">
<!ENTITY lock SYSTEM "lock.sgml">
<!ENTITY move SYSTEM "move.sgml">
-<!ENTITY merge SYSTEM "merge.sgml">
<!ENTITY notify SYSTEM "notify.sgml">
<!ENTITY prepare SYSTEM "prepare.sgml">
<!ENTITY prepareTransaction SYSTEM "prepare_transaction.sgml">
diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml
index 32f39a48ba9..0e35b0ef43e 100644
--- a/doc/src/sgml/ref/create_policy.sgml
+++ b/doc/src/sgml/ref/create_policy.sgml
@@ -94,13 +94,6 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
exist, a <quote>default deny</quote> policy is assumed, so that no rows will
be visible or updatable.
</para>
-
- <para>
- No separate policy exists for <command>MERGE</command>. Instead policies
- defined for <literal>SELECT</literal>, <literal>INSERT</literal>,
- <literal>UPDATE</literal> and <literal>DELETE</literal> are applied
- while executing MERGE, depending on the actions that are activated.
- </para>
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index da294aaa46a..62e142fd8ef 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -579,13 +579,6 @@ INSERT <replaceable>oid</replaceable> <replaceable class="parameter">count</repl
is a partition, an error will occur if one of the input rows violates
the partition constraint.
</para>
-
- <para>
- You may also wish to consider using <command>MERGE</command>, since that
- allows mixed <command>INSERT</command>, <command>UPDATE</command> and
- <command>DELETE</command> within a single statement.
- See <xref linkend="sql-merge"/>.
- </para>
</refsect1>
<refsect1>
@@ -756,9 +749,7 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International')
Also, the case in
which a column name list is omitted, but not all the columns are
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
- is disallowed by the standard. If you prefer a more SQL Standard
- conforming statement than <literal>ON CONFLICT</literal>, see
- <xref linkend="sql-merge"/>.
+ is disallowed by the standard.
</para>
<para>
diff --git a/doc/src/sgml/ref/merge.sgml b/doc/src/sgml/ref/merge.sgml
deleted file mode 100644
index b2a9f67cfa9..00000000000
--- a/doc/src/sgml/ref/merge.sgml
+++ /dev/null
@@ -1,617 +0,0 @@
-<!--
-doc/src/sgml/ref/merge.sgml
-PostgreSQL documentation
--->
-
-<refentry id="sql-merge">
-
- <refmeta>
- <refentrytitle>MERGE</refentrytitle>
- <manvolnum>7</manvolnum>
- <refmiscinfo>SQL - Language Statements</refmiscinfo>
- </refmeta>
-
- <refnamediv>
- <refname>MERGE</refname>
- <refpurpose>insert, update, or delete rows of a table based upon source data</refpurpose>
- </refnamediv>
-
- <refsynopsisdiv>
-<synopsis>
-[ WITH <replaceable class="parameter">with_query</replaceable> [, ...] ]
-MERGE INTO <replaceable class="parameter">target_table_name</replaceable> [ [ AS ] <replaceable class="parameter">target_alias</replaceable> ]
-USING <replaceable class="parameter">data_source</replaceable>
-ON <replaceable class="parameter">join_condition</replaceable>
-<replaceable class="parameter">when_clause</replaceable> [...]
-
-where <replaceable class="parameter">data_source</replaceable> is
-
-{ <replaceable class="parameter">source_table_name</replaceable> |
- ( source_query )
-}
-[ [ AS ] <replaceable class="parameter">source_alias</replaceable> ]
-
-and <replaceable class="parameter">when_clause</replaceable> is
-
-{ WHEN MATCHED [ AND <replaceable class="parameter">condition</replaceable> ] THEN { <replaceable class="parameter">merge_update</replaceable> | <replaceable class="parameter">merge_delete</replaceable> } |
- WHEN NOT MATCHED [ AND <replaceable class="parameter">condition</replaceable> ] THEN { <replaceable class="parameter">merge_insert</replaceable> | DO NOTHING }
-}
-
-and <replaceable class="parameter">merge_insert</replaceable> is
-
-INSERT [( <replaceable class="parameter">column_name</replaceable> [, ...] )]
-[ OVERRIDING { SYSTEM | USER } VALUE ]
-{ VALUES ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) | DEFAULT VALUES }
-
-and <replaceable class="parameter">merge_update</replaceable> is
-
-UPDATE SET { <replaceable class="parameter">column_name</replaceable> = { <replaceable class="parameter">expression</replaceable> | DEFAULT } |
- ( <replaceable class="parameter">column_name</replaceable> [, ...] ) = ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] )
- } [, ...]
-
-and <replaceable class="parameter">merge_delete</replaceable> is
-
-DELETE
-</synopsis>
- </refsynopsisdiv>
-
- <refsect1>
- <title>Description</title>
-
- <para>
- <command>MERGE</command> performs actions that modify rows in the
- <replaceable class="parameter">target_table_name</replaceable>,
- using the <replaceable class="parameter">data_source</replaceable>.
- <command>MERGE</command> provides a single <acronym>SQL</acronym>
- statement that can conditionally <command>INSERT</command>,
- <command>UPDATE</command> or <command>DELETE</command> rows, a task
- that would otherwise require multiple procedural language statements.
- </para>
-
- <para>
- First, the <command>MERGE</command> command performs a join
- from <replaceable class="parameter">data_source</replaceable> to
- <replaceable class="parameter">target_table_name</replaceable>
- producing zero or more candidate change rows. For each candidate change
- row the status of <literal>MATCHED</literal> or <literal>NOT MATCHED</literal> is set
- just once, after which <literal>WHEN</literal> clauses are evaluated
- in the order specified. If one of them is activated, the specified
- action occurs. No more than one <literal>WHEN</literal> clause can be
- activated for any candidate change row.
- </para>
-
- <para>
- <command>MERGE</command> actions have the same effect as
- regular <command>UPDATE</command>, <command>INSERT</command>, or
- <command>DELETE</command> commands of the same names. The syntax of
- those commands is different, notably that there is no <literal>WHERE</literal>
- clause and no tablename is specified. All actions refer to the
- <replaceable class="parameter">target_table_name</replaceable>,
- though modifications to other tables may be made using triggers.
- </para>
-
- <para>
- When <literal>DO NOTHING</literal> action is specified, the source row is
- skipped. Since actions are evaluated in the given order, <literal>DO
- NOTHING</literal> can be handy to skip non-interesting source rows before
- more fine-grained handling.
- </para>
-
- <para>
- There is no MERGE privilege.
- You must have the <literal>UPDATE</literal> privilege on the column(s)
- of the <replaceable class="parameter">target_table_name</replaceable>
- referred to in the <literal>SET</literal> clause
- if you specify an update action, the <literal>INSERT</literal> privilege
- on the <replaceable class="parameter">target_table_name</replaceable>
- if you specify an insert action and/or the <literal>DELETE</literal>
- privilege on the <replaceable class="parameter">target_table_name</replaceable>
- if you specify a delete action on the
- <replaceable class="parameter">target_table_name</replaceable>.
- Privileges are tested once at statement start and are checked
- whether or not particular <literal>WHEN</literal> clauses are activated
- during the subsequent execution.
- You will require the <literal>SELECT</literal> privilege on the
- <replaceable class="parameter">data_source</replaceable> and any column(s)
- of the <replaceable class="parameter">target_table_name</replaceable>
- referred to in a <literal>condition</literal>.
- </para>
-
- <para>
- MERGE is not supported if the <replaceable
- class="parameter">target_table_name</replaceable> has
- <literal>RULES</literal> defined on it.
- See <xref linkend="rules"/> for more information about <literal>RULES</literal>.
- </para>
- </refsect1>
-
- <refsect1>
- <title>Parameters</title>
-
- <variablelist>
- <varlistentry>
- <term><replaceable class="parameter">target_table_name</replaceable></term>
- <listitem>
- <para>
- The name (optionally schema-qualified) of the target table to merge into.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">target_alias</replaceable></term>
- <listitem>
- <para>
- A substitute name for the target table. When an alias is
- provided, it completely hides the actual name of the table. For
- example, given <literal>MERGE foo AS f</literal>, the remainder of the
- <command>MERGE</command> statement must refer to this table as
- <literal>f</literal> not <literal>foo</literal>.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">source_table_name</replaceable></term>
- <listitem>
- <para>
- The name (optionally schema-qualified) of the source table, view or
- transition table.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">source_query</replaceable></term>
- <listitem>
- <para>
- A query (<command>SELECT</command> statement or <command>VALUES</command>
- statement) that supplies the rows to be merged into the
- <replaceable class="parameter">target_table_name</replaceable>.
- Refer to the <xref linkend="sql-select"/>
- statement or <xref linkend="sql-values"/>
- statement for a description of the syntax.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">source_alias</replaceable></term>
- <listitem>
- <para>
- A substitute name for the data source. When an alias is
- provided, it completely hides whether table or query was specified.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">join_condition</replaceable></term>
- <listitem>
- <para>
- <replaceable class="parameter">join_condition</replaceable> is
- an expression resulting in a value of type
- <type>boolean</type> (similar to a <literal>WHERE</literal>
- clause) that specifies which rows in the
- <replaceable class="parameter">data_source</replaceable>
- match rows in the
- <replaceable class="parameter">target_table_name</replaceable>.
- </para>
- <warning>
- <para>
- Only columns from <replaceable class="parameter">target_table_name</replaceable>
- that attempt to match <replaceable class="parameter">data_source</replaceable>
- rows should appear in <replaceable class="parameter">join_condition</replaceable>.
- <replaceable class="parameter">join_condition</replaceable> subexpressions that
- only reference <replaceable class="parameter">target_table_name</replaceable>
- columns can only affect which action is taken, often in surprising ways.
- </para>
- </warning>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">when_clause</replaceable></term>
- <listitem>
- <para>
- At least one <literal>WHEN</literal> clause is required.
- </para>
- <para>
- If the <literal>WHEN</literal> clause specifies <literal>WHEN MATCHED</literal>
- and the candidate change row matches a row in the
- <replaceable class="parameter">target_table_name</replaceable>
- the <literal>WHEN</literal> clause is activated if the
- <replaceable class="parameter">condition</replaceable> is
- absent or is present and evaluates to <literal>true</literal>.
- If the <literal>WHEN</literal> clause specifies <literal>WHEN NOT MATCHED</literal>
- and the candidate change row does not match a row in the
- <replaceable class="parameter">target_table_name</replaceable>
- the <literal>WHEN</literal> clause is activated if the
- <replaceable class="parameter">condition</replaceable> is
- absent or is present and evaluates to <literal>true</literal>.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">condition</replaceable></term>
- <listitem>
- <para>
- An expression that returns a value of type <type>boolean</type>.
- If this expression returns <literal>true</literal> then the <literal>WHEN</literal>
- clause will be activated and the corresponding action will occur for
- that row. The expression may not contain functions that possibly performs
- writes to the database.
- </para>
- <para>
- A condition on a <literal>WHEN MATCHED</literal> clause can refer to columns
- in both the source and the target relation. A condition on a
- <literal>WHEN NOT MATCHED</literal> clause can only refer to columns from
- the source relation, since by definition there is no matching target row.
- Only the system attributes from the target table are accessible.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">merge_insert</replaceable></term>
- <listitem>
- <para>
- The specification of an <literal>INSERT</literal> action that inserts
- one row into the target table.
- The target column names can be listed in any order. If no list of
- column names is given at all, the default is all the columns of the
- table in their declared order.
- </para>
- <para>
- Each column not present in the explicit or implicit column list will be
- filled with a default value, either its declared default value
- or null if there is none.
- </para>
- <para>
- If the expression for any column is not of the correct data type,
- automatic type conversion will be attempted.
- </para>
- <para>
- If <replaceable class="parameter">target_table_name</replaceable>
- is a partitioned table, each row is routed to the appropriate partition
- and inserted into it.
- If <replaceable class="parameter">target_table_name</replaceable>
- is a partition, an error will occur if one of the input rows violates
- the partition constraint.
- </para>
- <para>
- Column names may not be specified more than once.
- <command>INSERT</command> actions cannot contain sub-selects.
- </para>
- <para>
- Only one <literal>VALUES</literal> clause can be specified.
- The <literal>VALUES</literal> clause can only refer to columns from
- the source relation, since by definition there is no matching target row.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">merge_update</replaceable></term>
- <listitem>
- <para>
- The specification of an <literal>UPDATE</literal> action that updates
- the current row of the <replaceable
- class="parameter">target_table_name</replaceable>.
- Column names may not be specified more than once.
- </para>
- <para>
- Do not include the table name, as you would normally do with an
- <xref linkend="sql-update"/> command.
- For example, <literal>UPDATE tab SET col = 1</literal> is invalid. Also,
- do not include a <literal>WHERE</literal> clause, since only the current
- row can be updated. For example,
- <literal>UPDATE SET col = 1 WHERE key = 57</literal> is invalid.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">merge_delete</replaceable></term>
- <listitem>
- <para>
- Specifies a <literal>DELETE</literal> action that deletes the current row
- of the <replaceable class="parameter">target_table_name</replaceable>.
- Do not include the tablename or any other clauses, as you would normally
- do with an <xref linkend="sql-delete"/> command.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">column_name</replaceable></term>
- <listitem>
- <para>
- The name of a column in the <replaceable
- class="parameter">target_table_name</replaceable>. The column name
- can be qualified with a subfield name or array subscript, if
- needed. (Inserting into only some fields of a composite
- column leaves the other fields null.) When referencing a
- column, do not include the table's name in the specification
- of a target column.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><literal>OVERRIDING SYSTEM VALUE</literal></term>
- <listitem>
- <para>
- Without this clause, it is an error to specify an explicit value
- (other than <literal>DEFAULT</literal>) for an identity column defined
- as <literal>GENERATED ALWAYS</literal>. This clause overrides that
- restriction.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><literal>OVERRIDING USER VALUE</literal></term>
- <listitem>
- <para>
- If this clause is specified, then any values supplied for identity
- columns defined as <literal>GENERATED BY DEFAULT</literal> are ignored
- and the default sequence-generated values are applied.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><literal>DEFAULT VALUES</literal></term>
- <listitem>
- <para>
- All columns will be filled with their default values.
- (An <literal>OVERRIDING</literal> clause is not permitted in this
- form.)
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">expression</replaceable></term>
- <listitem>
- <para>
- An expression to assign to the column. The expression can use the
- old values of this and other columns in the table.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><literal>DEFAULT</literal></term>
- <listitem>
- <para>
- Set the column to its default value (which will be NULL if no
- specific default expression has been assigned to it).
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry>
- <term><replaceable class="parameter">with_query</replaceable></term>
- <listitem>
- <para>
- The <literal>WITH</literal> clause allows you to specify one or more
- subqueries that can be referenced by name in the <command>MERGE</command>
- query. See <xref linkend="queries-with"/> and <xref linkend="sql-select"/>
- for details.
- </para>
- </listitem>
- </varlistentry>
-
- </variablelist>
- </refsect1>
-
- <refsect1>
- <title>Outputs</title>
-
- <para>
- On successful completion, a <command>MERGE</command> command returns a command
- tag of the form
-<screen>
-MERGE <replaceable class="parameter">total-count</replaceable>
-</screen>
- The <replaceable class="parameter">total-count</replaceable> is the total
- number of rows changed (whether inserted, updated, or deleted).
- If <replaceable class="parameter">total-count</replaceable> is 0, no rows
- were changed in any way.
- </para>
-
- </refsect1>
-
- <refsect1>
- <title>Execution</title>
-
- <para>
- The following steps take place during the execution of
- <command>MERGE</command>.
- <orderedlist>
- <listitem>
- <para>
- Perform any BEFORE STATEMENT triggers for all actions specified, whether or
- not their <literal>WHEN</literal> clauses are activated during execution.
- </para>
- </listitem>
- <listitem>
- <para>
- Perform a join from source to target table.
- The resulting query will be optimized normally and will produce
- a set of candidate change row. For each candidate change row
- <orderedlist>
- <listitem>
- <para>
- Evaluate whether each row is MATCHED or NOT MATCHED.
- </para>
- </listitem>
- <listitem>
- <para>
- Test each WHEN condition in the order specified until one activates.
- </para>
- </listitem>
- <listitem>
- <para>
- When activated, perform the following actions
- <orderedlist>
- <listitem>
- <para>
- Perform any BEFORE ROW triggers that fire for the action's event type.
- </para>
- </listitem>
- <listitem>
- <para>
- Apply the action specified, invoking any check constraints on the
- target table.
- However, it will not invoke rules.
- </para>
- </listitem>
- <listitem>
- <para>
- Perform any AFTER ROW triggers that fire for the action's event type.
- </para>
- </listitem>
- </orderedlist>
- </para>
- </listitem>
- </orderedlist>
- </para>
- </listitem>
- <listitem>
- <para>
- Perform any AFTER STATEMENT triggers for actions specified, whether or
- not they actually occur. This is similar to the behavior of an
- <command>UPDATE</command> statement that modifies no rows.
- </para>
- </listitem>
- </orderedlist>
- In summary, statement triggers for an event type (say, INSERT) will
- be fired whenever we <emphasis>specify</emphasis> an action of that kind. Row-level
- triggers will fire only for the one event type <emphasis>activated</emphasis>.
- So a <command>MERGE</command> might fire statement triggers for both
- <command>UPDATE</command> and <command>INSERT</command>, even though only
- <command>UPDATE</command> row triggers were fired.
- </para>
-
- <para>
- You should ensure that the join produces at most one candidate change row
- for each target row. In other words, a target row shouldn't join to more
- than one data source row. If it does, then only one of the candidate change
- rows will be used to modify the target row, later attempts to modify will
- cause an error. This can also occur if row triggers make changes to the
- target table which are then subsequently modified by <command>MERGE</command>.
- If the repeated action is an <command>INSERT</command> this will
- cause a uniqueness violation while a repeated <command>UPDATE</command> or
- <command>DELETE</command> will cause a cardinality violation; the latter behavior
- is required by the <acronym>SQL</acronym> Standard. This differs from
- historical <productname>PostgreSQL</productname> behavior of joins in
- <command>UPDATE</command> and <command>DELETE</command> statements where second and
- subsequent attempts to modify are simply ignored.
- </para>
-
- <para>
- If a <literal>WHEN</literal> clause omits an <literal>AND</literal> clause it becomes
- the final reachable clause of that kind (<literal>MATCHED</literal> or
- <literal>NOT MATCHED</literal>). If a later <literal>WHEN</literal> clause of that kind
- is specified it would be provably unreachable and an error is raised.
- If a final reachable clause is omitted it is possible that no action
- will be taken for a candidate change row.
- </para>
-
- </refsect1>
- <refsect1>
- <title>Notes</title>
-
- <para>
- The order in which rows are generated from the data source is indeterminate
- by default. A <replaceable class="parameter">source_query</replaceable>
- can be used to specify a consistent ordering, if required, which might be
- needed to avoid deadlocks between concurrent transactions.
- </para>
-
- <para>
- There is no <literal>RETURNING</literal> clause with <command>MERGE</command>.
- Actions of <command>INSERT</command>, <command>UPDATE</command> and <command>DELETE</command>
- cannot contain <literal>RETURNING</literal> or <literal>WITH</literal> clauses.
- </para>
-
- <tip>
- <para>
- You may also wish to consider using <command>INSERT ... ON CONFLICT</command> as an
- alternative statement which offers the ability to run an <command>UPDATE</command>
- if a concurrent <command>INSERT</command> occurs. There are a variety of
- differences and restrictions between the two statement types and they are not
- interchangeable.
- </para>
- </tip>
- </refsect1>
-
- <refsect1>
- <title>Examples</title>
-
- <para>
- Perform maintenance on CustomerAccounts based upon new Transactions.
-
-<programlisting>
-MERGE CustomerAccount CA
-USING RecentTransactions T
-ON T.CustomerId = CA.CustomerId
-WHEN MATCHED THEN
- UPDATE SET Balance = Balance + TransactionValue
-WHEN NOT MATCHED THEN
- INSERT (CustomerId, Balance)
- VALUES (T.CustomerId, T.TransactionValue);
-</programlisting>
-
- notice that this would be exactly equivalent to the following
- statement because the <literal>MATCHED</literal> result does not change
- during execution
-
-<programlisting>
-MERGE CustomerAccount CA
-USING (Select CustomerId, TransactionValue From RecentTransactions) AS T
-ON CA.CustomerId = T.CustomerId
-WHEN NOT MATCHED THEN
- INSERT (CustomerId, Balance)
- VALUES (T.CustomerId, T.TransactionValue)
-WHEN MATCHED THEN
- UPDATE SET Balance = Balance + TransactionValue;
-</programlisting>
- </para>
-
- <para>
- Attempt to insert a new stock item along with the quantity of stock. If
- the item already exists, instead update the stock count of the existing
- item. Don't allow entries that have zero stock.
-<programlisting>
-MERGE INTO wines w
-USING wine_stock_changes s
-ON s.winename = w.winename
-WHEN NOT MATCHED AND s.stock_delta > 0 THEN
- INSERT VALUES(s.winename, s.stock_delta)
-WHEN MATCHED AND w.stock + s.stock_delta > 0 THEN
- UPDATE SET stock = w.stock + s.stock_delta;
-WHEN MATCHED THEN
- DELETE;
-</programlisting>
-
- The wine_stock_changes table might be, for example, a temporary table
- recently loaded into the database.
- </para>
-
- </refsect1>
-
- <refsect1>
- <title>Compatibility</title>
- <para>
- This command conforms to the <acronym>SQL</acronym> standard.
- </para>
- <para>
- The WITH clause and DO NOTHING action are extensions to the <acronym>SQL</acronym> standard.
- </para>
- </refsect1>
-</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ef2270c4673..d27fb414f7c 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -186,7 +186,6 @@
&listen;
&load;
&lock;
- &merge;
&move;
&notify;
&prepare;
diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index cce58fbf1d0..c43dbc9786e 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -183,26 +183,6 @@
</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
triggers can return a table row (a value of
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index fa415ab06f9..4fdb549099c 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3248,7 +3248,6 @@ l1:
result == HeapTupleUpdated ||
result == HeapTupleBeingUpdated);
Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
- hufd->result = result;
hufd->ctid = tp.t_data->t_ctid;
hufd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
if (result == HeapTupleSelfUpdated)
@@ -3519,7 +3518,7 @@ simple_heap_delete(Relation relation, ItemPointer tid)
HTSU_Result
heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
CommandId cid, Snapshot crosscheck, bool wait,
- HeapUpdateFailureData *hufd)
+ HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
{
HTSU_Result result;
TransactionId xid = GetCurrentTransactionId();
@@ -3559,10 +3558,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
infomask2_old_tuple,
infomask_new_tuple,
infomask2_new_tuple;
- LockTupleMode lockmode;
Assert(ItemPointerIsValid(otid));
- Assert(hufd != NULL);
/*
* Forbid this during a parallel operation, lest it allocate a combocid.
@@ -3678,7 +3675,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
*/
if (!bms_overlap(modified_attrs, key_attrs))
{
- lockmode = hufd->lockmode = LockTupleNoKeyExclusive;
+ *lockmode = LockTupleNoKeyExclusive;
mxact_status = MultiXactStatusNoKeyUpdate;
key_intact = true;
@@ -3695,7 +3692,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
}
else
{
- lockmode = hufd->lockmode = LockTupleExclusive;
+ *lockmode = LockTupleExclusive;
mxact_status = MultiXactStatusUpdate;
key_intact = false;
}
@@ -3773,12 +3770,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 */
@@ -3862,7 +3859,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);
@@ -3901,7 +3898,6 @@ l2:
result == HeapTupleUpdated ||
result == HeapTupleBeingUpdated);
Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
- hufd->result = result;
hufd->ctid = oldtup.t_data->t_ctid;
hufd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
if (result == HeapTupleSelfUpdated)
@@ -3910,7 +3906,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);
@@ -3948,7 +3944,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);
@@ -4065,7 +4061,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);
@@ -4377,7 +4373,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);
@@ -4601,11 +4597,12 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
{
HTSU_Result result;
HeapUpdateFailureData hufd;
+ LockTupleMode lockmode;
result = heap_update(relation, otid, tup,
GetCurrentCommandId(true), InvalidSnapshot,
true /* wait for commit */ ,
- &hufd);
+ &hufd, &lockmode);
switch (result)
{
case HeapTupleSelfUpdated:
@@ -5191,7 +5188,6 @@ failed:
Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
result == HeapTupleWouldBlock);
Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
- hufd->result = result;
hufd->ctid = tuple->t_data->t_ctid;
hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
if (result == HeapTupleSelfUpdated)
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index ca0409b83ea..20d61f37803 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -229,9 +229,9 @@ F311 Schema definition statement 02 CREATE TABLE for persistent base tables YES
F311 Schema definition statement 03 CREATE VIEW YES
F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION YES
F311 Schema definition statement 05 GRANT statement YES
-F312 MERGE statement YES also consider INSERT ... ON CONFLICT DO UPDATE
-F313 Enhanced MERGE statement YES
-F314 MERGE statement with DELETE branch YES
+F312 MERGE statement NO consider INSERT ... ON CONFLICT DO UPDATE
+F313 Enhanced MERGE statement NO
+F314 MERGE statement with DELETE branch NO
F321 User authorization YES
F341 Usage tables NO no ROUTINE_*_USAGE tables
F361 Subprogram support YES
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 306e6444669..e1a62a1bce9 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -946,9 +946,6 @@ ExplainNode(PlanState *planstate, List *ancestors,
case CMD_DELETE:
pname = operation = "Delete";
break;
- case CMD_MERGE:
- pname = operation = "Merge";
- break;
default:
pname = "???";
break;
@@ -3011,10 +3008,6 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
operation = "Delete";
foperation = "Foreign Delete";
break;
- case CMD_MERGE:
- operation = "Merge";
- foperation = "Foreign Merge";
- break;
default:
operation = "???";
foperation = "Foreign ???";
@@ -3137,32 +3130,6 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
other_path, 0, es);
}
}
- else if (node->operation == CMD_MERGE)
- {
- /* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
- if (es->analyze && mtstate->ps.instrument)
- {
- double total;
- double insert_path;
- double update_path;
- double delete_path;
- double skipped_path;
-
- InstrEndLoop(mtstate->mt_plans[0]->instrument);
-
- /* count the number of source rows */
- total = mtstate->mt_plans[0]->instrument->ntuples;
- insert_path = mtstate->ps.instrument->nfiltered1;
- update_path = mtstate->ps.instrument->nfiltered2;
- delete_path = mtstate->ps.instrument->nfiltered3;
- skipped_path = total - insert_path - update_path - delete_path;
-
- ExplainPropertyFloat("Tuples Inserted", NULL, insert_path, 0, es);
- ExplainPropertyFloat("Tuples Updated", NULL, update_path, 0, es);
- ExplainPropertyFloat("Tuples Deleted", NULL, delete_path, 0, es);
- ExplainPropertyFloat("Tuples Skipped", NULL, skipped_path, 0, es);
- }
- }
if (labeltargets)
ExplainCloseGroup("Target Tables", "Target Tables", false, es);
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index c3610b18741..b945b1556a8 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -151,7 +151,6 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString,
case CMD_INSERT:
case CMD_UPDATE:
case CMD_DELETE:
- case CMD_MERGE:
/* OK */
break;
default:
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 0d57d467484..cc50691aa00 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -84,8 +84,7 @@ static HeapTuple GetTupleForTrigger(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tid,
LockTupleMode lockmode,
- TupleTableSlot **newSlot,
- HeapUpdateFailureData *hufdp);
+ TupleTableSlot **newSlot);
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
Trigger *trigger, TriggerEvent event,
Bitmapset *modifiedCols,
@@ -95,12 +94,6 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
FmgrInfo *finfo,
Instrumentation *instr,
MemoryContext per_tuple_context);
-static Tuplestorestate *AfterTriggerGetTransitionTable(int event,
- HeapTuple oldtup,
- HeapTuple newtup,
- TransitionCaptureState *transition_capture);
-static void TransitionTableAddTuple(HeapTuple heaptup, Tuplestorestate *tuplestore,
- TupleConversionMap *map);
static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup,
@@ -2736,8 +2729,7 @@ bool
ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
- HeapTuple fdw_trigtuple,
- HeapUpdateFailureData *hufdp)
+ HeapTuple fdw_trigtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
bool result = true;
@@ -2751,7 +2743,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
if (fdw_trigtuple == NULL)
{
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
- LockTupleExclusive, &newSlot, hufdp);
+ LockTupleExclusive, &newSlot);
if (trigtuple == NULL)
return false;
}
@@ -2822,7 +2814,6 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
relinfo,
tupleid,
LockTupleExclusive,
- NULL,
NULL);
else
trigtuple = fdw_trigtuple;
@@ -2960,8 +2951,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
- TupleTableSlot *slot,
- HeapUpdateFailureData *hufdp)
+ TupleTableSlot *slot)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
HeapTuple slottuple = ExecMaterializeSlot(slot);
@@ -2982,7 +2972,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
{
/* get a copy of the on-disk tuple we are planning to update */
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
- lockmode, &newSlot, hufdp);
+ lockmode, &newSlot);
if (trigtuple == NULL)
return NULL; /* cancel the update action */
}
@@ -3102,7 +3092,6 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
relinfo,
tupleid,
LockTupleExclusive,
- NULL,
NULL);
else
trigtuple = fdw_trigtuple;
@@ -3251,8 +3240,7 @@ GetTupleForTrigger(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tid,
LockTupleMode lockmode,
- TupleTableSlot **newSlot,
- HeapUpdateFailureData *hufdp)
+ TupleTableSlot **newSlot)
{
Relation relation = relinfo->ri_RelationDesc;
HeapTupleData tuple;
@@ -3278,11 +3266,6 @@ ltrmark:;
estate->es_output_cid,
lockmode, LockWaitBlock,
false, &buffer, &hufd);
-
- /* Let the caller know about failure reason, if any. */
- if (hufdp)
- *hufdp = hufd;
-
switch (test)
{
case HeapTupleSelfUpdated:
@@ -3324,17 +3307,10 @@ ltrmark:;
/* it was updated, so look at the updated version */
TupleTableSlot *epqslot;
- /*
- * If we're running MERGE then we must install the
- * new tuple in the slot of the underlying join query and
- * not the result relation itself. If the join does not
- * yield any tuple, the caller will take the necessary
- * action.
- */
epqslot = EvalPlanQual(estate,
epqstate,
relation,
- GetEPQRangeTableIndex(relinfo),
+ relinfo->ri_RangeTableIndex,
lockmode,
&hufd.ctid,
hufd.xmax);
@@ -3857,22 +3833,8 @@ struct AfterTriggersTableData
bool before_trig_done; /* did we already queue BS triggers? */
bool after_trig_done; /* did we already queue AS triggers? */
AfterTriggerEventList after_trig_events; /* if so, saved list pointer */
-
- /*
- * We maintain separate transaction tables for UPDATE/INSERT/DELETE since
- * MERGE can run all three actions in a single statement. Note that UPDATE
- * needs both old and new transition tables whereas INSERT needs only new
- * and DELETE needs only old.
- */
-
- /* "old" transition table for UPDATE, if any */
- Tuplestorestate *old_upd_tuplestore;
- /* "new" transition table for UPDATE, if any */
- Tuplestorestate *new_upd_tuplestore;
- /* "old" transition table for DELETE, if any */
- Tuplestorestate *old_del_tuplestore;
- /* "new" transition table INSERT, if any */
- Tuplestorestate *new_ins_tuplestore;
+ Tuplestorestate *old_tuplestore; /* "old" transition table, if any */
+ Tuplestorestate *new_tuplestore; /* "new" transition table, if any */
};
static AfterTriggersData afterTriggers;
@@ -4339,19 +4301,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
{
if (LocTriggerData.tg_trigger->tgoldtable)
{
- if (TRIGGER_FIRED_BY_UPDATE(evtshared->ats_event))
- LocTriggerData.tg_oldtable = evtshared->ats_table->old_upd_tuplestore;
- else
- LocTriggerData.tg_oldtable = evtshared->ats_table->old_del_tuplestore;
+ LocTriggerData.tg_oldtable = evtshared->ats_table->old_tuplestore;
evtshared->ats_table->closed = true;
}
if (LocTriggerData.tg_trigger->tgnewtable)
{
- if (TRIGGER_FIRED_BY_INSERT(evtshared->ats_event))
- LocTriggerData.tg_newtable = evtshared->ats_table->new_ins_tuplestore;
- else
- LocTriggerData.tg_newtable = evtshared->ats_table->new_upd_tuplestore;
+ LocTriggerData.tg_newtable = evtshared->ats_table->new_tuplestore;
evtshared->ats_table->closed = true;
}
}
@@ -4686,10 +4642,8 @@ TransitionCaptureState *
MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
{
TransitionCaptureState *state;
- bool need_old_upd,
- need_new_upd,
- need_old_del,
- need_new_ins;
+ bool need_old,
+ need_new;
AfterTriggersTableData *table;
MemoryContext oldcxt;
ResourceOwner saveResourceOwner;
@@ -4701,31 +4655,23 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
switch (cmdType)
{
case CMD_INSERT:
- need_old_upd = need_old_del = need_new_upd = false;
- need_new_ins = trigdesc->trig_insert_new_table;
+ need_old = false;
+ need_new = trigdesc->trig_insert_new_table;
break;
case CMD_UPDATE:
- need_old_upd = trigdesc->trig_update_old_table;
- need_new_upd = trigdesc->trig_update_new_table;
- need_old_del = need_new_ins = false;
+ need_old = trigdesc->trig_update_old_table;
+ need_new = trigdesc->trig_update_new_table;
break;
case CMD_DELETE:
- need_old_del = trigdesc->trig_delete_old_table;
- need_old_upd = need_new_upd = need_new_ins = false;
- break;
- case CMD_MERGE:
- need_old_upd = trigdesc->trig_update_old_table;
- need_new_upd = trigdesc->trig_update_new_table;
- need_old_del = trigdesc->trig_delete_old_table;
- need_new_ins = trigdesc->trig_insert_new_table;
+ need_old = trigdesc->trig_delete_old_table;
+ need_new = false;
break;
default:
elog(ERROR, "unexpected CmdType: %d", (int) cmdType);
- /* keep compiler quiet */
- need_old_upd = need_new_upd = need_old_del = need_new_ins = false;
+ need_old = need_new = false; /* keep compiler quiet */
break;
}
- if (!need_old_upd && !need_new_upd && !need_new_ins && !need_old_del)
+ if (!need_old && !need_new)
return NULL;
/* Check state, like AfterTriggerSaveEvent. */
@@ -4755,14 +4701,10 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
saveResourceOwner = CurrentResourceOwner;
CurrentResourceOwner = CurTransactionResourceOwner;
- if (need_old_upd && table->old_upd_tuplestore == NULL)
- table->old_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
- if (need_new_upd && table->new_upd_tuplestore == NULL)
- table->new_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
- if (need_old_del && table->old_del_tuplestore == NULL)
- table->old_del_tuplestore = tuplestore_begin_heap(false, false, work_mem);
- if (need_new_ins && table->new_ins_tuplestore == NULL)
- table->new_ins_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+ if (need_old && table->old_tuplestore == NULL)
+ table->old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+ if (need_new && table->new_tuplestore == NULL)
+ table->new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
CurrentResourceOwner = saveResourceOwner;
MemoryContextSwitchTo(oldcxt);
@@ -4951,20 +4893,12 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
{
AfterTriggersTableData *table = (AfterTriggersTableData *) lfirst(lc);
- ts = table->old_upd_tuplestore;
- table->old_upd_tuplestore = NULL;
- if (ts)
- tuplestore_end(ts);
- ts = table->new_upd_tuplestore;
- table->new_upd_tuplestore = NULL;
- if (ts)
- tuplestore_end(ts);
- ts = table->old_del_tuplestore;
- table->old_del_tuplestore = NULL;
+ ts = table->old_tuplestore;
+ table->old_tuplestore = NULL;
if (ts)
tuplestore_end(ts);
- ts = table->new_ins_tuplestore;
- table->new_ins_tuplestore = NULL;
+ ts = table->new_tuplestore;
+ table->new_tuplestore = NULL;
if (ts)
tuplestore_end(ts);
}
@@ -5735,84 +5669,6 @@ AfterTriggerPendingOnRel(Oid relid)
return false;
}
-/*
- * Get the transition table for the given event and depending on whether we are
- * processing the old or the new tuple.
- */
-static Tuplestorestate *
-AfterTriggerGetTransitionTable(int event,
- HeapTuple oldtup,
- HeapTuple newtup,
- TransitionCaptureState *transition_capture)
-{
- Tuplestorestate *tuplestore = NULL;
- bool delete_old_table = transition_capture->tcs_delete_old_table;
- bool update_old_table = transition_capture->tcs_update_old_table;
- bool update_new_table = transition_capture->tcs_update_new_table;
- bool insert_new_table = transition_capture->tcs_insert_new_table;;
-
- /*
- * For INSERT events newtup should be non-NULL, for DELETE events
- * oldtup should be non-NULL, whereas for UPDATE events normally both
- * oldtup and newtup are non-NULL. But for UPDATE events fired for
- * capturing transition tuples during UPDATE partition-key row
- * movement, oldtup is NULL when the event is for a row being inserted,
- * whereas newtup is NULL when the event is for a row being deleted.
- */
- Assert(!(event == TRIGGER_EVENT_DELETE && delete_old_table &&
- oldtup == NULL));
- Assert(!(event == TRIGGER_EVENT_INSERT && insert_new_table &&
- newtup == NULL));
-
- /*
- * We're called either for the newtup or the oldtup, but not both at the
- * same time.
- */
- Assert((oldtup != NULL) ^ (newtup != NULL));
-
- if (oldtup != NULL)
- {
- if (event == TRIGGER_EVENT_DELETE && delete_old_table)
- tuplestore = transition_capture->tcs_private->old_del_tuplestore;
- else if (event == TRIGGER_EVENT_UPDATE && update_old_table)
- tuplestore = transition_capture->tcs_private->old_upd_tuplestore;
- }
-
- if (newtup != NULL)
- {
- if (event == TRIGGER_EVENT_INSERT && insert_new_table)
- tuplestore = transition_capture->tcs_private->new_ins_tuplestore;
- else if (event == TRIGGER_EVENT_UPDATE && update_new_table)
- tuplestore = transition_capture->tcs_private->new_upd_tuplestore;
- }
-
- return tuplestore;
-}
-
-/*
- * Add the given heap tuple to the given tuplestore, applying the conversion
- * map if necessary.
- */
-static void
-TransitionTableAddTuple(HeapTuple heaptup, Tuplestorestate *tuplestore,
- TupleConversionMap *map)
-{
- /*
- * Nothing needs to be done if we don't have a tuplestore.
- */
- if (tuplestore == NULL)
- return;
-
- if (map != NULL)
- {
- HeapTuple converted = do_convert_tuple(heaptup, map);
-
- tuplestore_puttuple(tuplestore, converted);
- pfree(converted);
- }
- else
- tuplestore_puttuple(tuplestore, heaptup);
-}
/* ----------
* AfterTriggerSaveEvent()
@@ -5874,37 +5730,61 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
{
HeapTuple original_insert_tuple = transition_capture->tcs_original_insert_tuple;
TupleConversionMap *map = transition_capture->tcs_map;
+ bool delete_old_table = transition_capture->tcs_delete_old_table;
+ bool update_old_table = transition_capture->tcs_update_old_table;
+ bool update_new_table = transition_capture->tcs_update_new_table;
+ bool insert_new_table = transition_capture->tcs_insert_new_table;;
/*
- * Capture the old tuple in the appropriate transition table based on
- * the event.
+ * For INSERT events newtup should be non-NULL, for DELETE events
+ * oldtup should be non-NULL, whereas for UPDATE events normally both
+ * oldtup and newtup are non-NULL. But for UPDATE events fired for
+ * capturing transition tuples during UPDATE partition-key row
+ * movement, oldtup is NULL when the event is for a row being inserted,
+ * whereas newtup is NULL when the event is for a row being deleted.
*/
- if (oldtup != NULL)
+ Assert(!(event == TRIGGER_EVENT_DELETE && delete_old_table &&
+ oldtup == NULL));
+ Assert(!(event == TRIGGER_EVENT_INSERT && insert_new_table &&
+ newtup == NULL));
+
+ if (oldtup != NULL &&
+ ((event == TRIGGER_EVENT_DELETE && delete_old_table) ||
+ (event == TRIGGER_EVENT_UPDATE && update_old_table)))
{
- Tuplestorestate *tuplestore =
- AfterTriggerGetTransitionTable(event,
- oldtup,
- NULL,
- transition_capture);
- TransitionTableAddTuple(oldtup, tuplestore, map);
- }
+ Tuplestorestate *old_tuplestore;
- /*
- * Capture the new tuple in the appropriate transition table based on
- * the event.
- */
- if (newtup != NULL)
+ old_tuplestore = transition_capture->tcs_private->old_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) ||
+ (event == TRIGGER_EVENT_UPDATE && update_new_table)))
{
- Tuplestorestate *tuplestore =
- AfterTriggerGetTransitionTable(event,
- NULL,
- newtup,
- transition_capture);
+ Tuplestorestate *new_tuplestore;
+
+ new_tuplestore = transition_capture->tcs_private->new_tuplestore;
if (original_insert_tuple != NULL)
- tuplestore_puttuple(tuplestore, original_insert_tuple);
+ 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
- TransitionTableAddTuple(newtup, tuplestore, map);
+ tuplestore_puttuple(new_tuplestore, newtup);
}
/*
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 76d87eea49c..cc09895fa5c 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
execGrouping.o execIndexing.o execJunk.o \
- execMain.o execMerge.o execParallel.o execPartition.o execProcnode.o \
+ execMain.o execParallel.o execPartition.o execProcnode.o \
execReplication.o execScan.o execSRF.o execTuples.o \
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
nodeBitmapAnd.o nodeBitmapOr.o \
diff --git a/src/backend/executor/README b/src/backend/executor/README
index 67736805c26..0d7cd552eb6 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -37,17 +37,6 @@ the plan tree returns the computed tuples to be updated, plus a "junk"
one. For DELETE, the plan tree need only deliver a CTID column, and the
ModifyTable node visits each of those rows and marks the row deleted.
-MERGE runs one generic plan that returns candidate target rows. Each row
-consists of a super-row that contains all the columns needed by any of the
-individual actions, plus CTID and 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 partitioned table. When a matching target tuple is found,
-the CTID column identifies the matching tuple and we attempt to activate
-WHEN MATCHED actions. If a matching tuple is not found, then CTID column is
-NULL and we attempt to activate WHEN NOT MATCHED actions. Once we know which
-action is activated we form the final result row and apply only those changes.
-
XXX a great deal more documentation needs to be written here...
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 13ad92745e0..4fa713bbe5e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -233,7 +233,6 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
case CMD_INSERT:
case CMD_DELETE:
case CMD_UPDATE:
- case CMD_MERGE:
estate->es_output_cid = GetCurrentCommandId(true);
break;
@@ -1351,9 +1350,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_onConflictArbiterIndexes = NIL;
resultRelInfo->ri_onConflict = NULL;
- resultRelInfo->ri_mergeTargetRTI = 0;
- resultRelInfo->ri_mergeState = (MergeState *) palloc0(sizeof (MergeState));
-
/*
* Partition constraint, which also includes the partition constraint of
* all the ancestors that are partitions. Note that it will be checked
@@ -2203,19 +2199,6 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
errmsg("new row violates row-level security policy for table \"%s\"",
wco->relname)));
break;
- case WCO_RLS_MERGE_UPDATE_CHECK:
- case WCO_RLS_MERGE_DELETE_CHECK:
- if (wco->polname != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("target row violates row-level security policy \"%s\" (USING expression) for table \"%s\"",
- wco->polname, wco->relname)));
- else
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("target row violates row-level security policy (USING expression) for table \"%s\"",
- wco->relname)));
- break;
case WCO_RLS_CONFLICT_CHECK:
if (wco->polname != NULL)
ereport(ERROR,
diff --git a/src/backend/executor/execMerge.c b/src/backend/executor/execMerge.c
deleted file mode 100644
index d75d7e5ab26..00000000000
--- a/src/backend/executor/execMerge.c
+++ /dev/null
@@ -1,682 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * execMerge.c
- * routines to handle Merge nodes relating to the MERGE command
- *
- * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- * src/backend/executor/execMerge.c
- *
- *-------------------------------------------------------------------------
- */
-
-
-#include "postgres.h"
-
-#include "access/htup_details.h"
-#include "access/xact.h"
-#include "commands/trigger.h"
-#include "executor/execPartition.h"
-#include "executor/executor.h"
-#include "executor/nodeModifyTable.h"
-#include "executor/execMerge.h"
-#include "miscadmin.h"
-#include "nodes/nodeFuncs.h"
-#include "storage/bufmgr.h"
-#include "storage/lmgr.h"
-#include "utils/builtins.h"
-#include "utils/memutils.h"
-#include "utils/rel.h"
-#include "utils/tqual.h"
-
-static void ExecMergeNotMatched(ModifyTableState *mtstate, EState *estate,
- TupleTableSlot *slot);
-static bool ExecMergeMatched(ModifyTableState *mtstate, EState *estate,
- TupleTableSlot *slot, JunkFilter *junkfilter,
- ItemPointer tupleid);
-/*
- * Perform MERGE.
- */
-void
-ExecMerge(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot,
- JunkFilter *junkfilter, ResultRelInfo *resultRelInfo)
-{
- ExprContext *econtext = mtstate->ps.ps_ExprContext;
- ItemPointer tupleid;
- ItemPointerData tuple_ctid;
- bool matched = false;
- Datum datum;
- bool isNull;
-
- Assert(resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION ||
- resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
-
- /*
- * Reset per-tuple memory context to free any expression evaluation
- * storage allocated in the previous cycle.
- */
- ResetExprContext(econtext);
-
- /*
- * We run a JOIN between the target relation and the source relation to
- * find a set of candidate source rows that has matching row in the target
- * table and a set of candidate source rows that does not have matching
- * row in the target table. If the join returns us a tuple with target
- * relation's tid set, that implies that the join found a matching row for
- * the given source tuple. This case triggers the WHEN MATCHED clause of
- * the MERGE. Whereas a NULL in the target relation's ctid column
- * indicates a NOT MATCHED case.
- */
- datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, &isNull);
-
- if (!isNull)
- {
- matched = true;
- tupleid = (ItemPointer) DatumGetPointer(datum);
- tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
- tupleid = &tuple_ctid;
- }
- else
- {
- matched = false;
- tupleid = NULL; /* we don't need it for INSERT actions */
- }
-
- /*
- * If we are dealing with a WHEN MATCHED case, we execute the first action
- * for which the additional WHEN MATCHED AND quals pass. If an action
- * without quals is found, that action is executed.
- *
- * Similarly, if we are dealing with WHEN NOT MATCHED case, we look at the
- * given WHEN NOT MATCHED actions in sequence until one passes.
- *
- * Things get interesting in case of concurrent update/delete of the
- * target tuple. Such concurrent update/delete is detected while we are
- * executing a WHEN MATCHED action.
- *
- * A concurrent update can:
- *
- * 1. modify the target tuple so that it no longer satisfies the
- * additional quals attached to the current WHEN MATCHED action OR
- *
- * In this case, we are still dealing with a WHEN MATCHED case, but
- * we should recheck the list of WHEN MATCHED actions and choose the first
- * one that satisfies the new target tuple.
- *
- * 2. modify the target tuple so that the join quals no longer pass and
- * hence the source tuple no longer has a match.
- *
- * In the second case, the source tuple no longer matches the target tuple,
- * so we now instead find a qualifying WHEN NOT MATCHED action to execute.
- *
- * A concurrent delete, changes a WHEN MATCHED case to WHEN NOT MATCHED.
- *
- * ExecMergeMatched takes care of following the update chain and
- * re-finding the qualifying WHEN MATCHED action, as long as the updated
- * target tuple still satisfies the join quals i.e. it still remains a
- * WHEN MATCHED case. If the tuple gets deleted or the join quals fail, it
- * returns and we try ExecMergeNotMatched. Given that ExecMergeMatched
- * always make progress by following the update chain and we never switch
- * from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a
- * livelock.
- */
- if (matched)
- matched = ExecMergeMatched(mtstate, estate, slot, junkfilter, tupleid);
-
- /*
- * Either we were dealing with a NOT MATCHED tuple or ExecMergeNotMatched()
- * returned "false", indicating the previously MATCHED tuple is no longer a
- * matching tuple.
- */
- if (!matched)
- ExecMergeNotMatched(mtstate, estate, slot);
-}
-
-/*
- * Check and execute the first qualifying MATCHED action. The current target
- * tuple is identified by tupleid.
- *
- * We start from the first WHEN MATCHED action and check if the WHEN AND quals
- * pass, if any. If the WHEN AND quals for the first action do not pass, we
- * check the second, then the third and so on. If we reach to the end, no
- * action is taken and we return true, indicating that no further action is
- * required for this tuple.
- *
- * If we do find a qualifying action, then we attempt to execute the action.
- *
- * If the tuple is concurrently updated, EvalPlanQual is run with the updated
- * tuple to recheck the join quals. Note that the additional quals associated
- * with individual actions are evaluated separately by the MERGE code, while
- * EvalPlanQual checks for the join quals. If EvalPlanQual tells us that the
- * updated tuple still passes the join quals, then we restart from the first
- * action to look for a qualifying action. Otherwise, we return false meaning
- * that a NOT MATCHED action must now be executed for the current source tuple.
- */
-static bool
-ExecMergeMatched(ModifyTableState *mtstate, EState *estate,
- TupleTableSlot *slot, JunkFilter *junkfilter,
- ItemPointer tupleid)
-{
- ExprContext *econtext = mtstate->ps.ps_ExprContext;
- bool isNull;
- List *mergeMatchedActionStates = NIL;
- HeapUpdateFailureData hufd;
- bool tuple_updated,
- tuple_deleted;
- Buffer buffer;
- HeapTupleData tuple;
- EPQState *epqstate = &mtstate->mt_epqstate;
- ResultRelInfo *saved_resultRelInfo;
- ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
- ListCell *l;
- TupleTableSlot *saved_slot = slot;
-
- if (mtstate->mt_partition_tuple_routing)
- {
- Datum datum;
- Oid tableoid = InvalidOid;
- int leaf_part_index;
- PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
-
- /*
- * In case of partitioned table, we fetch the tableoid while performing
- * MATCHED MERGE action.
- */
- datum = ExecGetJunkAttribute(slot, junkfilter->jf_otherJunkAttNo,
- &isNull);
- Assert(!isNull);
- tableoid = DatumGetObjectId(datum);
-
- /*
- * If we're dealing with a MATCHED tuple, then tableoid must have been
- * set correctly. In case of partitioned table, we must now fetch the
- * correct result relation corresponding to the child table emitting
- * the matching target row. For normal table, there is just one result
- * relation and it must be the one emitting the matching row.
- */
- leaf_part_index = ExecFindPartitionByOid(proute, tableoid);
-
- resultRelInfo = proute->partitions[leaf_part_index];
- if (resultRelInfo == NULL)
- {
- resultRelInfo = ExecInitPartitionInfo(mtstate,
- mtstate->resultRelInfo,
- proute, estate, leaf_part_index);
- Assert(resultRelInfo != NULL);
- }
- }
-
- /*
- * Save the current information and work with the correct result relation.
- */
- saved_resultRelInfo = resultRelInfo;
- estate->es_result_relation_info = resultRelInfo;
-
- /*
- * And get the correct action lists.
- */
- mergeMatchedActionStates =
- resultRelInfo->ri_mergeState->matchedActionStates;
-
- /*
- * If there are not WHEN MATCHED actions, we are done.
- */
- if (mergeMatchedActionStates == NIL)
- return true;
-
- /*
- * Make tuple and any needed join variables available to ExecQual and
- * ExecProject. The target's existing tuple is installed in the scantuple.
- * Again, this target relation's slot is required only in the case of a
- * MATCHED tuple and UPDATE/DELETE actions.
- */
- if (mtstate->mt_partition_tuple_routing)
- ExecSetSlotDescriptor(mtstate->mt_existing,
- resultRelInfo->ri_RelationDesc->rd_att);
- econtext->ecxt_scantuple = mtstate->mt_existing;
- econtext->ecxt_innertuple = slot;
- econtext->ecxt_outertuple = NULL;
-
-lmerge_matched:;
- slot = saved_slot;
-
- /*
- * UPDATE/DELETE is only invoked for matched rows. And we must have found
- * the tupleid of the target row in that case. We fetch using SnapshotAny
- * because we might get called again after EvalPlanQual returns us a new
- * tuple. This tuple may not be visible to our MVCC snapshot.
- */
- Assert(tupleid != NULL);
-
- tuple.t_self = *tupleid;
- if (!heap_fetch(resultRelInfo->ri_RelationDesc, SnapshotAny, &tuple,
- &buffer, true, NULL))
- elog(ERROR, "Failed to fetch the target tuple");
-
- /* Store target's existing tuple in the state's dedicated slot */
- ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
-
- foreach(l, mergeMatchedActionStates)
- {
- MergeActionState *action = (MergeActionState *) lfirst(l);
-
- /*
- * Test condition, if any
- *
- * In the absence of a condition we perform the action unconditionally
- * (no need to check separately since ExecQual() will return true if
- * there are no conditions to evaluate).
- */
- if (!ExecQual(action->whenqual, econtext))
- continue;
-
- /*
- * Check if the existing target tuple meet the USING checks of
- * UPDATE/DELETE RLS policies. If those checks fail, we throw an
- * error.
- *
- * The WITH CHECK quals are applied in ExecUpdate() and hence we need
- * not do anything special to handle them.
- *
- * NOTE: We must do this after WHEN quals are evaluated so that we
- * check policies only when they matter.
- */
- if (resultRelInfo->ri_WithCheckOptions)
- {
- ExecWithCheckOptions(action->commandType == CMD_UPDATE ?
- WCO_RLS_MERGE_UPDATE_CHECK : WCO_RLS_MERGE_DELETE_CHECK,
- resultRelInfo,
- mtstate->mt_existing,
- mtstate->ps.state);
- }
-
- /* Perform stated action */
- switch (action->commandType)
- {
- case CMD_UPDATE:
-
- /*
- * We set up the projection earlier, so all we do here is
- * Project, no need for any other tasks prior to the
- * ExecUpdate.
- */
- if (mtstate->mt_partition_tuple_routing)
- ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc);
- ExecProject(action->proj);
-
- /*
- * We don't call ExecFilterJunk() because the projected tuple
- * using the UPDATE action's targetlist doesn't have a junk
- * attribute.
- */
- slot = ExecUpdate(mtstate, tupleid, NULL,
- mtstate->mt_mergeproj,
- slot, epqstate, estate,
- &tuple_updated, &hufd,
- action, mtstate->canSetTag);
- break;
-
- case CMD_DELETE:
- /* Nothing to Project for a DELETE action */
- slot = ExecDelete(mtstate, tupleid, NULL,
- slot, epqstate, estate,
- &tuple_deleted, false, &hufd, action,
- mtstate->canSetTag,
- false /* changingPart */);
-
- break;
-
- default:
- elog(ERROR, "unknown action in MERGE WHEN MATCHED clause");
-
- }
-
- /*
- * Check for any concurrent update/delete operation which may have
- * prevented our update/delete. We also check for situations where we
- * might be trying to update/delete the same tuple twice.
- */
- if ((action->commandType == CMD_UPDATE && !tuple_updated) ||
- (action->commandType == CMD_DELETE && !tuple_deleted))
-
- {
- switch (hufd.result)
- {
- case HeapTupleMayBeUpdated:
- break;
- case HeapTupleInvisible:
-
- /*
- * This state should never be reached since the underlying
- * JOIN runs with a MVCC snapshot and EvalPlanQual runs
- * with a dirty snapshot. So such a row should have never
- * been returned for MERGE.
- */
- elog(ERROR, "unexpected invisible tuple");
- break;
-
- case HeapTupleSelfUpdated:
-
- /*
- * SQLStandard disallows this for MERGE.
- */
- if (TransactionIdIsCurrentTransactionId(hufd.xmax))
- ereport(ERROR,
- (errcode(ERRCODE_CARDINALITY_VIOLATION),
- errmsg("MERGE command cannot affect row a second time"),
- errhint("Ensure that not more than one source row matches any one target row")));
- /* This shouldn't happen */
- elog(ERROR, "attempted to update or delete invisible tuple");
- break;
-
- case HeapTupleUpdated:
-
- /*
- * The target tuple was concurrently updated/deleted by
- * some other transaction.
- *
- * If the current tuple is that last tuple in the update
- * chain, then we know that the tuple was concurrently
- * deleted. Just return and let the caller try NOT MATCHED
- * actions.
- *
- * If the current tuple was concurrently updated, then we
- * must run the EvalPlanQual() with the new version of the
- * tuple. If EvalPlanQual() does not return a tuple then
- * we switch to the NOT MATCHED list of actions.
- * If it does return a tuple and the join qual is
- * still satisfied, then we just need to recheck the
- * MATCHED actions, starting from the top, and execute the
- * first qualifying action.
- */
- if (!ItemPointerEquals(tupleid, &hufd.ctid))
- {
- TupleTableSlot *epqslot;
-
- /*
- * Since we generate a JOIN query with a target table
- * RTE different than the result relation RTE, we must
- * pass in the RTI of the relation used in the join
- * query and not the one from result relation.
- */
- Assert(resultRelInfo->ri_mergeTargetRTI > 0);
- epqslot = EvalPlanQual(estate,
- epqstate,
- resultRelInfo->ri_RelationDesc,
- GetEPQRangeTableIndex(resultRelInfo),
- LockTupleExclusive,
- &hufd.ctid,
- hufd.xmax);
-
- if (!TupIsNull(epqslot))
- {
- (void) ExecGetJunkAttribute(epqslot,
- resultRelInfo->ri_junkFilter->jf_junkAttNo,
- &isNull);
-
- /*
- * A non-NULL ctid means that we are still dealing
- * with MATCHED case. But we must retry from the
- * start with the updated tuple to ensure that the
- * first qualifying WHEN MATCHED action is
- * executed.
- *
- * We don't use the new slot returned by
- * EvalPlanQual because we anyways re-install the
- * new target tuple in econtext->ecxt_scantuple
- * before re-evaluating WHEN AND conditions and
- * re-projecting the update targetlists. The
- * source side tuple does not change and hence we
- * can safely continue to use the old slot.
- */
- if (!isNull)
- {
- /*
- * Must update *tupleid to the TID of the
- * newer tuple found in the update chain.
- */
- *tupleid = hufd.ctid;
- ReleaseBuffer(buffer);
- goto lmerge_matched;
- }
- }
- }
-
- /*
- * Tell the caller about the updated TID, restore the
- * state back and return.
- */
- *tupleid = hufd.ctid;
- estate->es_result_relation_info = saved_resultRelInfo;
- ReleaseBuffer(buffer);
- return false;
-
- default:
- break;
-
- }
- }
-
- if (action->commandType == CMD_UPDATE && tuple_updated)
- InstrCountFiltered2(&mtstate->ps, 1);
- if (action->commandType == CMD_DELETE && tuple_deleted)
- InstrCountFiltered3(&mtstate->ps, 1);
-
- /*
- * We've activated one of the WHEN clauses, so we don't search
- * further. This is required behaviour, not an optimization.
- */
- estate->es_result_relation_info = saved_resultRelInfo;
- break;
- }
-
- ReleaseBuffer(buffer);
-
- /*
- * Successfully executed an action or no qualifying action was found.
- */
- return true;
-}
-
-/*
- * Execute the first qualifying NOT MATCHED action.
- */
-static void
-ExecMergeNotMatched(ModifyTableState *mtstate, EState *estate,
- TupleTableSlot *slot)
-{
- PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
- ExprContext *econtext = mtstate->ps.ps_ExprContext;
- List *mergeNotMatchedActionStates = NIL;
- ResultRelInfo *resultRelInfo;
- ListCell *l;
- TupleTableSlot *myslot;
-
- /*
- * We are dealing with NOT MATCHED tuple. Since for MERGE, the partition
- * tree is not expanded for the result relation, we continue to work with
- * the currently active result relation, which corresponds to the root
- * of the partition tree.
- */
- resultRelInfo = mtstate->resultRelInfo;
-
- /*
- * For INSERT actions, root relation's merge action is OK since the
- * INSERT's targetlist and the WHEN conditions can only refer to the
- * source relation and hence it does not matter which result relation we
- * work with.
- */
- mergeNotMatchedActionStates =
- resultRelInfo->ri_mergeState->notMatchedActionStates;
-
- /*
- * Make source tuple available to ExecQual and ExecProject. We don't need
- * the target tuple since the WHEN quals and the targetlist can't refer to
- * the target columns.
- */
- econtext->ecxt_scantuple = NULL;
- econtext->ecxt_innertuple = slot;
- econtext->ecxt_outertuple = NULL;
-
- foreach(l, mergeNotMatchedActionStates)
- {
- MergeActionState *action = (MergeActionState *) lfirst(l);
-
- /*
- * Test condition, if any
- *
- * In the absence of a condition we perform the action unconditionally
- * (no need to check separately since ExecQual() will return true if
- * there are no conditions to evaluate).
- */
- if (!ExecQual(action->whenqual, econtext))
- continue;
-
- /* Perform stated action */
- switch (action->commandType)
- {
- case CMD_INSERT:
-
- /*
- * We set up the projection earlier, so all we do here is
- * Project, no need for any other tasks prior to the
- * ExecInsert.
- */
- if (mtstate->mt_partition_tuple_routing)
- ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc);
- ExecProject(action->proj);
-
- /*
- * ExecPrepareTupleRouting may modify the passed-in slot. Hence
- * pass a local reference so that action->slot is not modified.
- */
- myslot = mtstate->mt_mergeproj;
-
- /* Prepare for tuple routing if needed. */
- if (proute)
- myslot = ExecPrepareTupleRouting(mtstate, estate, proute,
- resultRelInfo, myslot);
- slot = ExecInsert(mtstate, myslot, slot,
- estate, action,
- mtstate->canSetTag);
- /* Revert ExecPrepareTupleRouting's state change. */
- if (proute)
- estate->es_result_relation_info = resultRelInfo;
- InstrCountFiltered1(&mtstate->ps, 1);
- break;
- case CMD_NOTHING:
- /* Do Nothing */
- break;
- default:
- elog(ERROR, "unknown action in MERGE WHEN NOT MATCHED clause");
- }
-
- break;
- }
-}
-
-void
-ExecInitMerge(ModifyTableState *mtstate, EState *estate,
- ResultRelInfo *resultRelInfo)
-{
- ListCell *l;
- ExprContext *econtext;
- List *mergeMatchedActionStates = NIL;
- List *mergeNotMatchedActionStates = NIL;
- TupleDesc relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
- ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
-
- if (node->mergeActionList == NIL)
- return;
-
- 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;
- }
-}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index d4d54e927a5..11139f743d1 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -73,8 +73,6 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
ResultRelInfo *update_rri = NULL;
int num_update_rri = 0,
update_rri_index = 0;
- bool is_update = false;
- bool is_merge = false;
PartitionTupleRouting *proute;
int nparts;
ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
@@ -97,22 +95,13 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
/* Set up details specific to the type of tuple routing we are doing. */
if (node && node->operation == CMD_UPDATE)
- is_update = true;
- else if (node && node->operation == CMD_MERGE)
- is_merge = true;
-
- if (is_update)
{
update_rri = mtstate->resultRelInfo;
num_update_rri = list_length(node->plans);
proute->subplan_partition_offsets =
palloc(num_update_rri * sizeof(int));
proute->num_subplan_partition_offsets = num_update_rri;
- }
-
- if (is_update || is_merge)
- {
/*
* We need an additional tuple slot for storing transient tuples that
* are converted to the root table descriptor.
@@ -296,30 +285,6 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
}
/*
- * Given OID of the partition leaf, return the index of the leaf in the
- * partition hierarchy.
- *
- * XXX This is an O(N) operation and further optimization would be beneficial
- */
-int
-ExecFindPartitionByOid(PartitionTupleRouting *proute, Oid partoid)
-{
- int i;
-
- for (i = 0; i < proute->num_partitions; i++)
- {
- if (proute->partition_oids[i] == partoid)
- break;
- }
-
- if (i >= proute->num_partitions)
- ereport(ERROR,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg("no partition found for OID %u", partoid)));
- return i;
-}
-
-/*
* ExecInitPartitionInfo
* Initialize ResultRelInfo and other information for a partition if not
* already done
@@ -357,8 +322,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
rootrel,
estate->es_instrument);
- leaf_part_rri->ri_PartitionLeafIndex = partidx;
-
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
@@ -635,90 +598,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
Assert(proute->partitions[partidx] == NULL);
proute->partitions[partidx] = leaf_part_rri;
- /*
- * Initialize information about this partition that's needed to handle
- * MERGE.
- */
- if (node && node->operation == CMD_MERGE)
- {
- TupleDesc partrelDesc = RelationGetDescr(partrel);
- TupleConversionMap *map = proute->parent_child_tupconv_maps[partidx];
- int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
- Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
-
- /*
- * If the root parent and partition have the same tuple
- * descriptor, just reuse the original MERGE state for partition.
- */
- if (map == NULL)
- {
- leaf_part_rri->ri_mergeState = resultRelInfo->ri_mergeState;
- }
- else
- {
- /* Convert expressions contain partition's attnos. */
- List *conv_tl, *conv_qual;
- ListCell *l;
- List *matchedActionStates = NIL;
- List *notMatchedActionStates = NIL;
-
- foreach (l, node->mergeActionList)
- {
- MergeAction *action = lfirst_node(MergeAction, l);
- MergeActionState *action_state = makeNode(MergeActionState);
- TupleDesc tupDesc;
- ExprContext *econtext;
-
- action_state->matched = action->matched;
- action_state->commandType = action->commandType;
-
- conv_qual = (List *) action->qual;
- conv_qual = map_partition_varattnos(conv_qual,
- firstVarno, partrel,
- firstResultRel, NULL);
-
- action_state->whenqual = ExecInitQual(conv_qual, &mtstate->ps);
-
- conv_tl = (List *) action->targetList;
- conv_tl = map_partition_varattnos(conv_tl,
- firstVarno, partrel,
- firstResultRel, NULL);
-
- conv_tl = adjust_partition_tlist( conv_tl, map);
-
- tupDesc = ExecTypeFromTL(conv_tl, partrelDesc->tdhasoid);
- action_state->tupDesc = tupDesc;
-
- /* build action projection state */
- econtext = mtstate->ps.ps_ExprContext;
- action_state->proj =
- ExecBuildProjectionInfo(conv_tl, econtext,
- mtstate->mt_mergeproj,
- &mtstate->ps,
- partrelDesc);
-
- if (action_state->matched)
- matchedActionStates =
- lappend(matchedActionStates, action_state);
- else
- notMatchedActionStates =
- lappend(notMatchedActionStates, action_state);
- }
- leaf_part_rri->ri_mergeState->matchedActionStates =
- matchedActionStates;
- leaf_part_rri->ri_mergeState->notMatchedActionStates =
- notMatchedActionStates;
- }
-
- /*
- * get_partition_dispatch_recurse() and expand_partitioned_rtentry()
- * fetch the leaf OIDs in the same order. So we can safely derive the
- * index of the merge target relation corresponding to this partition
- * by simply adding partidx + 1 to the root's merge target relation.
- */
- leaf_part_rri->ri_mergeTargetRTI = node->mergeTargetRelation +
- partidx + 1;
- }
MemoryContextSwitchTo(oldContext);
return leaf_part_rri;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index b66346702dc..0333ccd0fed 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -464,7 +464,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
{
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
&searchslot->tts_tuple->t_self,
- NULL, slot, NULL);
+ NULL, slot);
if (slot == NULL) /* "do nothing" */
skip_tuple = true;
@@ -525,7 +525,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
{
skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
&searchslot->tts_tuple->t_self,
- NULL, NULL);
+ NULL);
}
if (!skip_tuple)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 543a735be2b..7ec2c6bcaa8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,7 +42,6 @@
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
-#include "executor/execMerge.h"
#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
@@ -63,6 +62,11 @@ 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);
@@ -81,7 +85,7 @@ static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
* The plan output is represented by its targetlist, because that makes
* handling the dropped-column case easier.
*/
-void
+static void
ExecCheckPlanOutput(Relation resultRel, List *targetList)
{
TupleDesc resultDesc = RelationGetDescr(resultRel);
@@ -255,12 +259,11 @@ ExecCheckTIDVisible(EState *estate,
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
-extern TupleTableSlot *
+static TupleTableSlot *
ExecInsert(ModifyTableState *mtstate,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
EState *estate,
- MergeActionState *actionState,
bool canSetTag)
{
HeapTuple tuple;
@@ -387,17 +390,9 @@ ExecInsert(ModifyTableState *mtstate,
* partition, we should instead check UPDATE policies, because we are
* executing policies defined on the target table, and not those
* defined on the child partitions.
- *
- * If we're running MERGE, we refer to the action that we're executing
- * to know if we're doing an INSERT or UPDATE to a partition table.
*/
- if (mtstate->operation == CMD_UPDATE)
- wco_kind = WCO_RLS_UPDATE_CHECK;
- else if (mtstate->operation == CMD_MERGE)
- wco_kind = (actionState->commandType == CMD_UPDATE) ?
- WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
- else
- wco_kind = WCO_RLS_INSERT_CHECK;
+ wco_kind = (mtstate->operation == CMD_UPDATE) ?
+ WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
/*
* ExecWithCheckOptions() will skip any WCOs which are not of the kind
@@ -622,19 +617,10 @@ ExecInsert(ModifyTableState *mtstate,
* passed to foreign table triggers; it is NULL when the foreign
* table has no relevant triggers.
*
- * MERGE passes actionState of the action it's currently executing;
- * regular DELETE passes NULL. This is used by ExecDelete to know if it's
- * being called from MERGE or regular DELETE operation.
- *
- * If the DELETE fails because the tuple is concurrently updated/deleted
- * by this or some other transaction, hufdp is filled with the reason as
- * well as other important information. Currently only MERGE needs this
- * information.
- *
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
-TupleTableSlot *
+static TupleTableSlot *
ExecDelete(ModifyTableState *mtstate,
ItemPointer tupleid,
HeapTuple oldtuple,
@@ -643,8 +629,6 @@ ExecDelete(ModifyTableState *mtstate,
EState *estate,
bool *tupleDeleted,
bool processReturning,
- HeapUpdateFailureData *hufdp,
- MergeActionState *actionState,
bool canSetTag,
bool changingPart)
{
@@ -659,14 +643,6 @@ ExecDelete(ModifyTableState *mtstate,
*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
*/
resultRelInfo = estate->es_result_relation_info;
@@ -679,7 +655,7 @@ ExecDelete(ModifyTableState *mtstate,
bool dodelete;
dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
- tupleid, oldtuple, hufdp);
+ tupleid, oldtuple);
if (!dodelete) /* "do nothing" */
return NULL;
@@ -747,15 +723,6 @@ ldelete:;
true /* wait for commit */ ,
&hufd,
changingPart);
-
- /*
- * 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:
@@ -790,11 +757,7 @@ ldelete:;
errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
- /*
- * Else, already deleted by self; nothing to do but inform
- * MERGE about it anyways so that it can take necessary
- * action.
- */
+ /* Else, already deleted by self; nothing to do */
return NULL;
case HeapTupleMayBeUpdated:
@@ -814,19 +777,10 @@ ldelete:;
{
TupleTableSlot *epqslot;
- /*
- * If we're executing MERGE, then the onus of running
- * EvalPlanQual() and handling its outcome lies with the
- * caller.
- */
- if (actionState != NULL)
- return NULL;
-
- /* Normal DELETE path. */
epqslot = EvalPlanQual(estate,
epqstate,
resultRelationDesc,
- GetEPQRangeTableIndex(resultRelInfo),
+ resultRelInfo->ri_RangeTableIndex,
LockTupleExclusive,
&hufd.ctid,
hufd.xmax);
@@ -836,12 +790,7 @@ ldelete:;
goto ldelete;
}
}
-
- /*
- * tuple already deleted; nothing to do. But MERGE might want
- * to handle it differently. We've already filled-in hufdp
- * with sufficient information for MERGE to look at.
- */
+ /* tuple already deleted; nothing to do */
return NULL;
default:
@@ -969,21 +918,10 @@ ldelete:;
* foreign table triggers; it is NULL when the foreign table has
* no relevant triggers.
*
- * MERGE passes actionState of the action it's currently executing;
- * regular UPDATE passes NULL. This is used by ExecUpdate to know if it's
- * being called from MERGE or regular UPDATE operation. ExecUpdate may
- * pass this information to ExecInsert if it ends up running DELETE+INSERT
- * for partition key updates.
- *
- * If the UPDATE fails because the tuple is concurrently updated/deleted
- * by this or some other transaction, hufdp is filled with the reason as
- * well as other important information. Currently only MERGE needs this
- * information.
- *
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
-extern TupleTableSlot *
+static TupleTableSlot *
ExecUpdate(ModifyTableState *mtstate,
ItemPointer tupleid,
HeapTuple oldtuple,
@@ -991,9 +929,6 @@ ExecUpdate(ModifyTableState *mtstate,
TupleTableSlot *planSlot,
EPQState *epqstate,
EState *estate,
- bool *tuple_updated,
- HeapUpdateFailureData *hufdp,
- MergeActionState *actionState,
bool canSetTag)
{
HeapTuple tuple;
@@ -1010,17 +945,6 @@ ExecUpdate(ModifyTableState *mtstate,
if (IsBootstrapProcessingMode())
elog(ERROR, "cannot UPDATE during bootstrap");
- if (tuple_updated)
- *tuple_updated = false;
-
- /*
- * Initialize hufdp. Since the caller is only interested in the failure
- * status, initialize with the state that is used to indicate successful
- * operation.
- */
- if (hufdp)
- hufdp->result = HeapTupleMayBeUpdated;
-
/*
* get the heap tuple out of the tuple table slot, making sure we have a
* writable copy
@@ -1038,7 +962,7 @@ ExecUpdate(ModifyTableState *mtstate,
resultRelInfo->ri_TrigDesc->trig_update_before_row)
{
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
- tupleid, oldtuple, slot, hufdp);
+ tupleid, oldtuple, slot);
if (slot == NULL) /* "do nothing" */
return NULL;
@@ -1084,6 +1008,7 @@ ExecUpdate(ModifyTableState *mtstate,
}
else
{
+ LockTupleMode lockmode;
bool partition_constraint_failed;
/*
@@ -1162,7 +1087,7 @@ lreplace:;
* processing. We want to return rows from INSERT.
*/
ExecDelete(mtstate, tupleid, oldtuple, planSlot, epqstate,
- estate, &tuple_deleted, false, hufdp, NULL,
+ estate, &tuple_deleted, false,
false /* canSetTag */, true /* changingPart */);
/*
@@ -1199,36 +1124,16 @@ lreplace:;
saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
/*
- * We should convert the tuple into root's tuple descriptor, since
- * ExecInsert() starts the search from root. To do that, we need to
- * retrieve the tuple conversion map for this resultRelInfo.
- *
- * If we're running MERGE then resultRelInfo is per-partition
- * resultRelInfo as initialized in ExecInitPartitionInfo(). Note
- * that we don't expand inheritance for the resultRelation in case
- * of MERGE and hence there is just one subplan. Whereas for
- * regular UPDATE, resultRelInfo is one of the per-subplan
- * resultRelInfos. In either case the position of this partition in
- * tracked in ri_PartitionLeafIndex;
- *
- * Retrieve the map either by looking at the resultRelInfo's
- * position in mtstate->resultRelInfo[] (for UPDATE) or by simply
- * using the ri_PartitionLeafIndex value (for MERGE).
+ * resultRelInfo is one of the per-subplan resultRelInfos. So we
+ * should convert the tuple into root's tuple descriptor, since
+ * ExecInsert() starts the search from root. The tuple conversion
+ * map list is in the order of mtstate->resultRelInfo[], so to
+ * retrieve the one for this resultRel, we need to know the
+ * position of the resultRel in mtstate->resultRelInfo[].
*/
- if (mtstate->operation == CMD_MERGE)
- {
- map_index = resultRelInfo->ri_PartitionLeafIndex;
- Assert(mtstate->rootResultRelInfo == NULL);
- tupconv_map = TupConvMapForLeaf(proute,
- mtstate->resultRelInfo,
- map_index);
- }
- else
- {
- map_index = resultRelInfo - mtstate->resultRelInfo;
- Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
- tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
- }
+ map_index = resultRelInfo - mtstate->resultRelInfo;
+ Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
+ tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
tuple = ConvertPartitionTupleSlot(tupconv_map,
tuple,
proute->root_tuple_slot,
@@ -1238,16 +1143,12 @@ lreplace:;
* Prepare for tuple routing, making it look like we're inserting
* into the root.
*/
+ Assert(mtstate->rootResultRelInfo != NULL);
slot = ExecPrepareTupleRouting(mtstate, estate, proute,
- getTargetResultRelInfo(mtstate),
- slot);
+ mtstate->rootResultRelInfo, slot);
ret_slot = ExecInsert(mtstate, slot, planSlot,
- estate, actionState, canSetTag);
-
- /* Update is successful. */
- if (tuple_updated)
- *tuple_updated = true;
+ estate, canSetTag);
/* Revert ExecPrepareTupleRouting's node change. */
estate->es_result_relation_info = resultRelInfo;
@@ -1285,16 +1186,7 @@ lreplace:;
estate->es_output_cid,
estate->es_crosscheck_snapshot,
true /* wait for commit */ ,
- &hufd);
-
- /*
- * Copy the necessary information, if the caller has asked for it. We
- * must do this irrespective of whether the tuple was updated or
- * deleted.
- */
- if (hufdp)
- *hufdp = hufd;
-
+ &hufd, &lockmode);
switch (result)
{
case HeapTupleSelfUpdated:
@@ -1348,37 +1240,22 @@ lreplace:;
{
TupleTableSlot *epqslot;
- /*
- * If we're executing MERGE, then the onus of running
- * EvalPlanQual() and handling its outcome lies with the
- * caller.
- */
- if (actionState != NULL)
- return NULL;
-
- /* Regular UPDATE path. */
epqslot = EvalPlanQual(estate,
epqstate,
resultRelationDesc,
- GetEPQRangeTableIndex(resultRelInfo),
- hufd.lockmode,
+ resultRelInfo->ri_RangeTableIndex,
+ lockmode,
&hufd.ctid,
hufd.xmax);
if (!TupIsNull(epqslot))
{
*tupleid = hufd.ctid;
- /* Normal UPDATE path */
slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
tuple = ExecMaterializeSlot(slot);
goto lreplace;
}
}
-
- /*
- * tuple already deleted; nothing to do. But MERGE might want
- * to handle it differently. We've already filled-in hufdp
- * with sufficient information for MERGE to look at.
- */
+ /* tuple already deleted; nothing to do */
return NULL;
default:
@@ -1407,9 +1284,6 @@ lreplace:;
estate, false, NULL, NIL);
}
- if (tuple_updated)
- *tuple_updated = true;
-
if (canSetTag)
(estate->es_processed)++;
@@ -1504,9 +1378,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
* there's no historical behavior to break.
*
* It is the user's responsibility to prevent this situation from
- * occurring. These problems are why SQL Standard similarly
- * specifies that for SQL MERGE, an exception must be raised in
- * the event of an attempt to update the same row twice.
+ * occurring. These problems are why SQL-2003 similarly specifies
+ * that for SQL MERGE, an exception must be raised in the event of
+ * an attempt to update the same row twice.
*/
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data)))
ereport(ERROR,
@@ -1636,7 +1510,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
mtstate->mt_conflproj, planSlot,
&mtstate->mt_epqstate, mtstate->ps.state,
- NULL, NULL, NULL, canSetTag);
+ canSetTag);
ReleaseBuffer(buffer);
return true;
@@ -1674,14 +1548,6 @@ fireBSTriggers(ModifyTableState *node)
case CMD_DELETE:
ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
break;
- case CMD_MERGE:
- if (node->mt_merge_subcommands & MERGE_INSERT)
- ExecBSInsertTriggers(node->ps.state, resultRelInfo);
- if (node->mt_merge_subcommands & MERGE_UPDATE)
- ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
- if (node->mt_merge_subcommands & MERGE_DELETE)
- ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
- break;
default:
elog(ERROR, "unknown operation");
break;
@@ -1737,17 +1603,6 @@ fireASTriggers(ModifyTableState *node)
ExecASDeleteTriggers(node->ps.state, resultRelInfo,
node->mt_transition_capture);
break;
- case CMD_MERGE:
- if (node->mt_merge_subcommands & MERGE_DELETE)
- ExecASDeleteTriggers(node->ps.state, resultRelInfo,
- node->mt_transition_capture);
- if (node->mt_merge_subcommands & MERGE_UPDATE)
- ExecASUpdateTriggers(node->ps.state, resultRelInfo,
- node->mt_transition_capture);
- if (node->mt_merge_subcommands & MERGE_INSERT)
- ExecASInsertTriggers(node->ps.state, resultRelInfo,
- node->mt_transition_capture);
- break;
default:
elog(ERROR, "unknown operation");
break;
@@ -1810,7 +1665,7 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
*
* Returns a slot holding the tuple of the partition rowtype.
*/
-TupleTableSlot *
+static TupleTableSlot *
ExecPrepareTupleRouting(ModifyTableState *mtstate,
EState *estate,
PartitionTupleRouting *proute,
@@ -2143,7 +1998,6 @@ ExecModifyTable(PlanState *pstate)
{
/* advance to next subplan if any */
node->mt_whichplan++;
-
if (node->mt_whichplan < node->mt_nplans)
{
resultRelInfo++;
@@ -2192,12 +2046,6 @@ ExecModifyTable(PlanState *pstate)
EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
slot = planSlot;
- if (operation == CMD_MERGE)
- {
- ExecMerge(node, estate, slot, junkfilter, resultRelInfo);
- continue;
- }
-
tupleid = NULL;
oldtuple = NULL;
if (junkfilter != NULL)
@@ -2279,20 +2127,19 @@ ExecModifyTable(PlanState *pstate)
slot = ExecPrepareTupleRouting(node, estate, proute,
resultRelInfo, slot);
slot = ExecInsert(node, slot, planSlot,
- estate, NULL, node->canSetTag);
+ estate, node->canSetTag);
/* Revert ExecPrepareTupleRouting's state change. */
if (proute)
estate->es_result_relation_info = resultRelInfo;
break;
case CMD_UPDATE:
slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
- &node->mt_epqstate, estate,
- NULL, NULL, NULL, node->canSetTag);
+ &node->mt_epqstate, estate, node->canSetTag);
break;
case CMD_DELETE:
slot = ExecDelete(node, tupleid, oldtuple, planSlot,
&node->mt_epqstate, estate,
- NULL, true, NULL, NULL, node->canSetTag,
+ NULL, true, node->canSetTag,
false /* changingPart */);
break;
default:
@@ -2383,16 +2230,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
saved_resultRelInfo = estate->es_result_relation_info;
resultRelInfo = mtstate->resultRelInfo;
-
- /*
- * mergeTargetRelation must be set if we're running MERGE and mustn't be
- * set if we're not.
- */
- Assert(operation != CMD_MERGE || node->mergeTargetRelation > 0);
- Assert(operation == CMD_MERGE || node->mergeTargetRelation == 0);
-
- resultRelInfo->ri_mergeTargetRTI = node->mergeTargetRelation;
-
i = 0;
foreach(l, node->plans)
{
@@ -2471,8 +2308,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* partition key.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
- (operation == CMD_INSERT || operation == CMD_MERGE ||
- update_tuple_routing_needed))
+ (operation == CMD_INSERT || update_tuple_routing_needed))
mtstate->mt_partition_tuple_routing =
ExecSetupPartitionTupleRouting(mtstate, rel);
@@ -2484,15 +2320,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
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
* descriptor of a source partition does not match the root partitioned
@@ -2684,10 +2511,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
}
- resultRelInfo = mtstate->resultRelInfo;
- if (mtstate->operation == CMD_MERGE)
- ExecInitMerge(mtstate, estate, resultRelInfo);
-
/* select first subplan */
mtstate->mt_whichplan = 0;
subplan = (Plan *) linitial(node->plans);
@@ -2701,7 +2524,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* --- no need to look first. Typically, this will be a 'ctid' or
* 'wholerow' attribute, but in the case of a foreign data wrapper it
* might be a set of junk attributes sufficient to identify the remote
- * row. We follow this logic for MERGE, so it always has a junk attributes.
+ * row.
*
* If there are multiple result relations, each one needs its own junk
* filter. Note multiple rels are only possible for UPDATE/DELETE, so we
@@ -2729,7 +2552,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
break;
case CMD_UPDATE:
case CMD_DELETE:
- case CMD_MERGE:
junk_filter_needed = true;
break;
default:
@@ -2745,7 +2567,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
JunkFilter *j;
subplan = mtstate->mt_plans[i]->plan;
-
if (operation == CMD_INSERT || operation == CMD_UPDATE)
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
subplan->targetlist);
@@ -2754,9 +2575,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
ExecInitExtraTupleSlot(estate, NULL));
- if (operation == CMD_UPDATE ||
- operation == CMD_DELETE ||
- operation == CMD_MERGE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
/* For UPDATE/DELETE, find the appropriate junk attr now */
char relkind;
@@ -2769,15 +2588,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo))
elog(ERROR, "could not find junk ctid column");
-
- if (operation == CMD_MERGE &&
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_otherJunkAttNo = ExecFindJunkAttribute(j, "tableoid");
- if (!AttributeNumberIsValid(j->jf_otherJunkAttNo))
- elog(ERROR, "could not find junk tableoid column");
-
- }
}
else if (relkind == RELKIND_FOREIGN_TABLE)
{
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index a49015e7cbc..08f6f67a15c 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2420,9 +2420,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
else
res = SPI_OK_UPDATE;
break;
- case CMD_MERGE:
- res = SPI_OK_MERGE;
- break;
default:
return SPI_ERROR_OPUNKNOWN;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b856fe29b59..7c045a7afef 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -207,7 +207,6 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(partitioned_rels);
COPY_SCALAR_FIELD(partColsUpdated);
COPY_NODE_FIELD(resultRelations);
- COPY_SCALAR_FIELD(mergeTargetRelation);
COPY_SCALAR_FIELD(resultRelIndex);
COPY_SCALAR_FIELD(rootResultRelIndex);
COPY_NODE_FIELD(plans);
@@ -223,8 +222,6 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(onConflictWhere);
COPY_SCALAR_FIELD(exclRelRTI);
COPY_NODE_FIELD(exclRelTlist);
- COPY_NODE_FIELD(mergeSourceTargetList);
- COPY_NODE_FIELD(mergeActionList);
return newnode;
}
@@ -2137,20 +2134,6 @@ _copyOnConflictExpr(const OnConflictExpr *from)
return newnode;
}
-static MergeAction *
-_copyMergeAction(const MergeAction *from)
-{
- MergeAction *newnode = makeNode(MergeAction);
-
- COPY_SCALAR_FIELD(matched);
- COPY_SCALAR_FIELD(commandType);
- COPY_SCALAR_FIELD(override);
- COPY_NODE_FIELD(qual);
- COPY_NODE_FIELD(targetList);
-
- return newnode;
-}
-
/*
* _copyPartitionPruneStepOp
*/
@@ -3030,9 +3013,6 @@ _copyQuery(const Query *from)
COPY_NODE_FIELD(setOperations);
COPY_NODE_FIELD(constraintDeps);
COPY_NODE_FIELD(withCheckOptions);
- COPY_SCALAR_FIELD(mergeTarget_relation);
- COPY_NODE_FIELD(mergeSourceTargetList);
- COPY_NODE_FIELD(mergeActionList);
COPY_LOCATION_FIELD(stmt_location);
COPY_LOCATION_FIELD(stmt_len);
@@ -3096,35 +3076,6 @@ _copyUpdateStmt(const UpdateStmt *from)
return newnode;
}
-static MergeStmt *
-_copyMergeStmt(const MergeStmt *from)
-{
- MergeStmt *newnode = makeNode(MergeStmt);
-
- COPY_NODE_FIELD(relation);
- COPY_NODE_FIELD(source_relation);
- COPY_NODE_FIELD(join_condition);
- COPY_NODE_FIELD(mergeWhenClauses);
- COPY_NODE_FIELD(withClause);
-
- return newnode;
-}
-
-static MergeWhenClause *
-_copyMergeWhenClause(const MergeWhenClause *from)
-{
- MergeWhenClause *newnode = makeNode(MergeWhenClause);
-
- COPY_SCALAR_FIELD(matched);
- COPY_SCALAR_FIELD(commandType);
- COPY_NODE_FIELD(condition);
- COPY_NODE_FIELD(targetList);
- COPY_NODE_FIELD(cols);
- COPY_NODE_FIELD(values);
- COPY_SCALAR_FIELD(override);
- return newnode;
-}
-
static SelectStmt *
_copySelectStmt(const SelectStmt *from)
{
@@ -5110,9 +5061,6 @@ copyObjectImpl(const void *from)
case T_OnConflictExpr:
retval = _copyOnConflictExpr(from);
break;
- case T_MergeAction:
- retval = _copyMergeAction(from);
- break;
case T_PartitionPruneStepOp:
retval = _copyPartitionPruneStepOp(from);
break;
@@ -5197,12 +5145,6 @@ copyObjectImpl(const void *from)
case T_UpdateStmt:
retval = _copyUpdateStmt(from);
break;
- case T_MergeStmt:
- retval = _copyMergeStmt(from);
- break;
- case T_MergeWhenClause:
- retval = _copyMergeWhenClause(from);
- break;
case T_SelectStmt:
retval = _copySelectStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 39946959afd..6a971d0141a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -812,18 +812,6 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b)
return true;
}
-
-static bool
-_equalMergeAction(const MergeAction *a, const MergeAction *b)
-{
- COMPARE_SCALAR_FIELD(matched);
- COMPARE_SCALAR_FIELD(commandType);
- COMPARE_SCALAR_FIELD(override);
- COMPARE_NODE_FIELD(qual);
- COMPARE_NODE_FIELD(targetList);
-
- return true;
-}
/*
* Stuff from relation.h
*/
@@ -989,8 +977,6 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_NODE_FIELD(setOperations);
COMPARE_NODE_FIELD(constraintDeps);
COMPARE_NODE_FIELD(withCheckOptions);
- COMPARE_NODE_FIELD(mergeSourceTargetList);
- COMPARE_NODE_FIELD(mergeActionList);
COMPARE_LOCATION_FIELD(stmt_location);
COMPARE_LOCATION_FIELD(stmt_len);
@@ -1047,32 +1033,6 @@ _equalUpdateStmt(const UpdateStmt *a, const UpdateStmt *b)
}
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(mergeWhenClauses);
- COMPARE_NODE_FIELD(withClause);
-
- return true;
-}
-
-static bool
-_equalMergeWhenClause(const MergeWhenClause *a, const MergeWhenClause *b)
-{
- COMPARE_SCALAR_FIELD(matched);
- COMPARE_SCALAR_FIELD(commandType);
- COMPARE_NODE_FIELD(condition);
- COMPARE_NODE_FIELD(targetList);
- COMPARE_NODE_FIELD(cols);
- COMPARE_NODE_FIELD(values);
- COMPARE_SCALAR_FIELD(override);
-
- return true;
-}
-
-static bool
_equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
{
COMPARE_NODE_FIELD(distinctClause);
@@ -3197,9 +3157,6 @@ equal(const void *a, const void *b)
case T_OnConflictExpr:
retval = _equalOnConflictExpr(a, b);
break;
- case T_MergeAction:
- retval = _equalMergeAction(a, b);
- break;
case T_JoinExpr:
retval = _equalJoinExpr(a, b);
break;
@@ -3265,12 +3222,6 @@ equal(const void *a, const void *b)
case T_UpdateStmt:
retval = _equalUpdateStmt(a, b);
break;
- case T_MergeStmt:
- retval = _equalMergeStmt(a, b);
- break;
- case T_MergeWhenClause:
- retval = _equalMergeWhenClause(a, b);
- break;
case T_SelectStmt:
retval = _equalSelectStmt(a, b);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 4c309d236a3..a10014f755b 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2146,16 +2146,6 @@ expression_tree_walker(Node *node,
return true;
}
break;
- case T_MergeAction:
- {
- MergeAction *action = (MergeAction *) node;
-
- if (walker(action->targetList, context))
- return true;
- if (walker(action->qual, context))
- return true;
- }
- break;
case T_PartitionPruneStepOp:
{
PartitionPruneStepOp *opstep = (PartitionPruneStepOp *) node;
@@ -2276,10 +2266,6 @@ query_tree_walker(Query *query,
return true;
if (walker((Node *) query->onConflict, context))
return true;
- if (walker((Node *) query->mergeSourceTargetList, context))
- return true;
- if (walker((Node *) query->mergeActionList, context))
- return true;
if (walker((Node *) query->returningList, context))
return true;
if (walker((Node *) query->jointree, context))
@@ -2957,18 +2943,6 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
- case T_MergeAction:
- {
- MergeAction *action = (MergeAction *) node;
- MergeAction *newnode;
-
- FLATCOPY(newnode, action, MergeAction);
- MUTATE(newnode->qual, action->qual, Node *);
- MUTATE(newnode->targetList, action->targetList, List *);
-
- return (Node *) newnode;
- }
- break;
case T_PartitionPruneStepOp:
{
PartitionPruneStepOp *opstep = (PartitionPruneStepOp *) node;
@@ -3134,8 +3108,6 @@ query_tree_mutator(Query *query,
MUTATE(query->targetList, query->targetList, List *);
MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
MUTATE(query->onConflict, query->onConflict, OnConflictExpr *);
- MUTATE(query->mergeSourceTargetList, query->mergeSourceTargetList, List *);
- MUTATE(query->mergeActionList, query->mergeActionList, List *);
MUTATE(query->returningList, query->returningList, List *);
MUTATE(query->jointree, query->jointree, FromExpr *);
MUTATE(query->setOperations, query->setOperations, Node *);
@@ -3277,9 +3249,9 @@ query_or_expression_tree_mutator(Node *node,
* boundaries: we descend to everything that's possibly interesting.
*
* Currently, the node type coverage here extends only to DML statements
- * (SELECT/INSERT/UPDATE/DELETE/MERGE) and nodes that can appear in them,
- * because this is used mainly during analysis of CTEs, and only DML
- * statements can appear in CTEs.
+ * (SELECT/INSERT/UPDATE/DELETE) and nodes that can appear in them, because
+ * this is used mainly during analysis of CTEs, and only DML statements can
+ * appear in CTEs.
*/
bool
raw_expression_tree_walker(Node *node,
@@ -3459,36 +3431,6 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
- case T_MergeStmt:
- {
- MergeStmt *stmt = (MergeStmt *) node;
-
- if (walker(stmt->relation, context))
- return true;
- if (walker(stmt->source_relation, context))
- return true;
- if (walker(stmt->join_condition, context))
- return true;
- if (walker(stmt->mergeWhenClauses, context))
- return true;
- if (walker(stmt->withClause, context))
- return true;
- }
- break;
- case T_MergeWhenClause:
- {
- MergeWhenClause *mergeWhenClause = (MergeWhenClause *) node;
-
- if (walker(mergeWhenClause->condition, context))
- return true;
- if (walker(mergeWhenClause->targetList, context))
- return true;
- if (walker(mergeWhenClause->cols, context))
- return true;
- if (walker(mergeWhenClause->values, context))
- return true;
- }
- break;
case T_SelectStmt:
{
SelectStmt *stmt = (SelectStmt *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ef626b34b3d..3991a0ce83a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -375,7 +375,6 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(partitioned_rels);
WRITE_BOOL_FIELD(partColsUpdated);
WRITE_NODE_FIELD(resultRelations);
- WRITE_INT_FIELD(mergeTargetRelation);
WRITE_INT_FIELD(resultRelIndex);
WRITE_INT_FIELD(rootResultRelIndex);
WRITE_NODE_FIELD(plans);
@@ -391,22 +390,6 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(onConflictWhere);
WRITE_UINT_FIELD(exclRelRTI);
WRITE_NODE_FIELD(exclRelTlist);
- WRITE_NODE_FIELD(mergeSourceTargetList);
- WRITE_NODE_FIELD(mergeActionList);
-}
-
-static void
-_outMergeWhenClause(StringInfo str, const MergeWhenClause *node)
-{
- WRITE_NODE_TYPE("MERGEWHENCLAUSE");
-
- WRITE_BOOL_FIELD(matched);
- WRITE_ENUM_FIELD(commandType, CmdType);
- WRITE_NODE_FIELD(condition);
- WRITE_NODE_FIELD(targetList);
- WRITE_NODE_FIELD(cols);
- WRITE_NODE_FIELD(values);
- WRITE_ENUM_FIELD(override, OverridingKind);
}
static void
@@ -1749,17 +1732,6 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
}
static void
-_outMergeAction(StringInfo str, const MergeAction *node)
-{
- WRITE_NODE_TYPE("MERGEACTION");
-
- WRITE_BOOL_FIELD(matched);
- WRITE_ENUM_FIELD(commandType, CmdType);
- WRITE_NODE_FIELD(qual);
- WRITE_NODE_FIELD(targetList);
-}
-
-static void
_outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
{
int i;
@@ -2189,7 +2161,6 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
WRITE_NODE_FIELD(partitioned_rels);
WRITE_BOOL_FIELD(partColsUpdated);
WRITE_NODE_FIELD(resultRelations);
- WRITE_INT_FIELD(mergeTargetRelation);
WRITE_NODE_FIELD(subpaths);
WRITE_NODE_FIELD(subroots);
WRITE_NODE_FIELD(withCheckOptionLists);
@@ -2197,8 +2168,6 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(onconflict);
WRITE_INT_FIELD(epqParam);
- WRITE_NODE_FIELD(mergeSourceTargetList);
- WRITE_NODE_FIELD(mergeActionList);
}
static void
@@ -3012,9 +2981,6 @@ _outQuery(StringInfo str, const Query *node)
WRITE_NODE_FIELD(setOperations);
WRITE_NODE_FIELD(constraintDeps);
/* withCheckOptions intentionally omitted, see comment in parsenodes.h */
- WRITE_INT_FIELD(mergeTarget_relation);
- WRITE_NODE_FIELD(mergeSourceTargetList);
- WRITE_NODE_FIELD(mergeActionList);
WRITE_LOCATION_FIELD(stmt_location);
WRITE_LOCATION_FIELD(stmt_len);
}
@@ -3733,9 +3699,6 @@ outNode(StringInfo str, const void *obj)
case T_ModifyTable:
_outModifyTable(str, obj);
break;
- case T_MergeWhenClause:
- _outMergeWhenClause(str, obj);
- break;
case T_Append:
_outAppend(str, obj);
break;
@@ -4012,9 +3975,6 @@ outNode(StringInfo str, const void *obj)
case T_OnConflictExpr:
_outOnConflictExpr(str, obj);
break;
- case T_MergeAction:
- _outMergeAction(str, obj);
- break;
case T_PartitionPruneStepOp:
_outPartitionPruneStepOp(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 93785e3bdfd..c466b98102e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -270,9 +270,6 @@ _readQuery(void)
READ_NODE_FIELD(setOperations);
READ_NODE_FIELD(constraintDeps);
/* withCheckOptions intentionally omitted, see comment in parsenodes.h */
- READ_INT_FIELD(mergeTarget_relation);
- READ_NODE_FIELD(mergeSourceTargetList);
- READ_NODE_FIELD(mergeActionList);
READ_LOCATION_FIELD(stmt_location);
READ_LOCATION_FIELD(stmt_len);
@@ -1357,22 +1354,6 @@ _readPartitionPruneStepCombine(void)
READ_DONE();
}
-/*
- * _readMergeAction
- */
-static MergeAction *
-_readMergeAction(void)
-{
- READ_LOCALS(MergeAction);
-
- READ_BOOL_FIELD(matched);
- READ_ENUM_FIELD(commandType, CmdType);
- READ_NODE_FIELD(qual);
- READ_NODE_FIELD(targetList);
-
- READ_DONE();
-}
-
static PartitionPruneInfo *
_readPartitionPruneInfo(void)
{
@@ -1638,7 +1619,6 @@ _readModifyTable(void)
READ_NODE_FIELD(partitioned_rels);
READ_BOOL_FIELD(partColsUpdated);
READ_NODE_FIELD(resultRelations);
- READ_INT_FIELD(mergeTargetRelation);
READ_INT_FIELD(resultRelIndex);
READ_INT_FIELD(rootResultRelIndex);
READ_NODE_FIELD(plans);
@@ -1654,27 +1634,6 @@ _readModifyTable(void)
READ_NODE_FIELD(onConflictWhere);
READ_UINT_FIELD(exclRelRTI);
READ_NODE_FIELD(exclRelTlist);
- READ_NODE_FIELD(mergeSourceTargetList);
- READ_NODE_FIELD(mergeActionList);
-
- READ_DONE();
-}
-
-/*
- * _readMergeWhenClause
- */
-static MergeWhenClause *
-_readMergeWhenClause(void)
-{
- READ_LOCALS(MergeWhenClause);
-
- READ_BOOL_FIELD(matched);
- READ_ENUM_FIELD(commandType, CmdType);
- READ_NODE_FIELD(condition);
- READ_NODE_FIELD(targetList);
- READ_NODE_FIELD(cols);
- READ_NODE_FIELD(values);
- READ_ENUM_FIELD(override, OverridingKind);
READ_DONE();
}
@@ -2657,8 +2616,6 @@ parseNodeString(void)
return_value = _readFromExpr();
else if (MATCH("ONCONFLICTEXPR", 14))
return_value = _readOnConflictExpr();
- else if (MATCH("MERGEACTION", 11))
- return_value = _readMergeAction();
else if (MATCH("PARTITIONPRUNESTEPOP", 20))
return_value = _readPartitionPruneStepOp();
else if (MATCH("PARTITIONPRUNESTEPCOMBINE", 25))
@@ -2687,8 +2644,6 @@ parseNodeString(void)
return_value = _readProjectSet();
else if (MATCH("MODIFYTABLE", 11))
return_value = _readModifyTable();
- else if (MATCH("MERGEWHENCLAUSE", 15))
- return_value = _readMergeWhenClause();
else if (MATCH("APPEND", 6))
return_value = _readAppend();
else if (MATCH("MERGEAPPEND", 11))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 048bdf4ad1e..b6ad01be6bb 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -289,13 +289,9 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
Index nominalRelation, List *partitioned_rels,
bool partColsUpdated,
- List *resultRelations,
- Index mergeTargetRelation,
- List *subplans,
+ List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
- List *rowMarks, OnConflictExpr *onconflict,
- List *mergeSourceTargetList,
- List *mergeActionList, int epqParam);
+ List *rowMarks, OnConflictExpr *onconflict, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
GatherMergePath *best_path);
@@ -2486,14 +2482,11 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
best_path->partitioned_rels,
best_path->partColsUpdated,
best_path->resultRelations,
- best_path->mergeTargetRelation,
subplans,
best_path->withCheckOptionLists,
best_path->returningLists,
best_path->rowMarks,
best_path->onconflict,
- best_path->mergeSourceTargetList,
- best_path->mergeActionList,
best_path->epqParam);
copy_generic_path_info(&plan->plan, &best_path->path);
@@ -6561,13 +6554,9 @@ make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
Index nominalRelation, List *partitioned_rels,
bool partColsUpdated,
- List *resultRelations,
- Index mergeTargetRelation,
- List *subplans,
+ List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
- List *rowMarks, OnConflictExpr *onconflict,
- List *mergeSourceTargetList,
- List *mergeActionList, int epqParam)
+ List *rowMarks, OnConflictExpr *onconflict, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list;
@@ -6593,7 +6582,6 @@ make_modifytable(PlannerInfo *root,
node->partitioned_rels = partitioned_rels;
node->partColsUpdated = partColsUpdated;
node->resultRelations = resultRelations;
- node->mergeTargetRelation = mergeTargetRelation;
node->resultRelIndex = -1; /* will be set correctly in setrefs.c */
node->rootResultRelIndex = -1; /* will be set correctly in setrefs.c */
node->plans = subplans;
@@ -6626,8 +6614,6 @@ make_modifytable(PlannerInfo *root,
node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
- node->mergeSourceTargetList = mergeSourceTargetList;
- node->mergeActionList = mergeActionList;
node->epqParam = epqParam;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 20f49e5d432..2e298f83573 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -794,24 +794,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
/* exclRelTlist contains only Vars, so no preprocessing needed */
}
- foreach(l, parse->mergeActionList)
- {
- MergeAction *action = (MergeAction *) lfirst(l);
-
- action->targetList = (List *)
- preprocess_expression(root,
- (Node *) action->targetList,
- EXPRKIND_TARGET);
- action->qual =
- preprocess_expression(root,
- (Node *) action->qual,
- EXPRKIND_QUAL);
- }
-
- parse->mergeSourceTargetList = (List *)
- preprocess_expression(root, (Node *) parse->mergeSourceTargetList,
- EXPRKIND_TARGET);
-
root->append_rel_list = (List *)
preprocess_expression(root, (Node *) root->append_rel_list,
EXPRKIND_APPINFO);
@@ -1564,7 +1546,6 @@ inheritance_planner(PlannerInfo *root)
subroot->parse->returningList);
Assert(!parse->onConflict);
- Assert(parse->mergeActionList == NIL);
}
/* Result path must go into outer query's FINAL upperrel */
@@ -1638,15 +1619,12 @@ inheritance_planner(PlannerInfo *root)
partitioned_rels,
root->partColsUpdated,
resultRelations,
- 0,
subpaths,
subroots,
withCheckOptionLists,
returningLists,
rowMarks,
NULL,
- NULL,
- NULL,
SS_assign_special_param(root)));
}
@@ -2177,8 +2155,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
}
/*
- * If this is an INSERT/UPDATE/DELETE/MERGE, and we're not being
- * called from inheritance_planner, add the ModifyTable node.
+ * If this is an INSERT/UPDATE/DELETE, and we're not being called from
+ * inheritance_planner, add the ModifyTable node.
*/
if (parse->commandType != CMD_SELECT && !inheritance_update)
{
@@ -2218,15 +2196,12 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
NIL,
false,
list_make1_int(parse->resultRelation),
- parse->mergeTarget_relation,
list_make1(path),
list_make1(root),
withCheckOptionLists,
returningLists,
rowMarks,
parse->onConflict,
- parse->mergeSourceTargetList,
- parse->mergeActionList,
SS_assign_special_param(root));
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 833a92f5387..69dd327f0c9 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -851,60 +851,6 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
fix_scan_list(root, splan->exclRelTlist, rtoffset);
}
- /*
- * The MERGE statement 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 statement
- * requires access to the columns from the source relation. We
- * arrange things so that the source relation attributes are
- * available as INNER_VAR and the target relation attributes
- * are available from the scan tuple.
- */
- if (splan->mergeActionList != NIL)
- {
- /*
- * mergeSourceTargetList is already setup correctly to
- * include all Vars coming from the source relation. So we
- * fix the targetList of individual action nodes by
- * ensuring that the source relation Vars are referenced
- * as INNER_VAR. Note that for this to work correctly,
- * during execution, the ecxt_innertuple must be set to
- * the tuple obtained from the source relation.
- *
- * We leave the Vars from the result relation (i.e. the
- * target relation) unchanged i.e. those Vars would be
- * picked from the scan slot. So during execution, we must
- * ensure that ecxt_scantuple is setup correctly to refer
- * to the tuple from the target relation.
- */
-
- indexed_tlist *itlist;
-
- itlist = build_tlist_index(splan->mergeSourceTargetList);
-
- splan->mergeTargetRelation += rtoffset;
-
- foreach(l, splan->mergeActionList)
- {
- MergeAction *action = (MergeAction *) lfirst(l);
-
- /* Fix targetList of each action. */
- action->targetList = fix_join_expr(root,
- action->targetList,
- NULL, itlist,
- linitial_int(splan->resultRelations),
- rtoffset);
-
- /* Fix quals too. */
- action->qual = (Node *) fix_join_expr(root,
- (List *) action->qual,
- NULL, itlist,
- linitial_int(splan->resultRelations),
- rtoffset);
- }
- }
-
splan->nominalRelation += rtoffset;
splan->exclRelRTI += rtoffset;
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 8a87cfd14ae..8603feef2b8 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -118,46 +118,6 @@ preprocess_targetlist(PlannerInfo *root)
tlist = expand_targetlist(tlist, command_type,
result_relation, target_relation);
- if (command_type == CMD_MERGE)
- {
- ListCell *l;
-
- /*
- * For MERGE, add any junk column(s) needed to allow the executor to
- * identify the rows to be updated or deleted, with different
- * handling for partitioned tables.
- */
- rewriteTargetListMerge(parse, target_relation);
-
- /*
- * For MERGE command, handle targetlist of each MergeAction separately.
- * Give the same treatment to MergeAction->targetList as we would have
- * given to a regular INSERT/UPDATE/DELETE.
- */
- foreach(l, parse->mergeActionList)
- {
- MergeAction *action = (MergeAction *) lfirst(l);
-
- switch (action->commandType)
- {
- case CMD_INSERT:
- case CMD_UPDATE:
- action->targetList = expand_targetlist(action->targetList,
- action->commandType,
- result_relation,
- target_relation);
- break;
- case CMD_DELETE:
- break;
- case CMD_NOTHING:
- break;
- default:
- elog(ERROR, "unknown action in MERGE WHEN clause");
-
- }
- }
- }
-
/*
* Add necessary junk columns for rowmarked rels. These values are needed
* for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
@@ -388,7 +348,6 @@ expand_targetlist(List *tlist, int command_type,
true /* byval */ );
}
break;
- case CMD_MERGE:
case CMD_UPDATE:
if (!att_tup->attisdropped)
{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index bd9442c22d6..776c592dbe7 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3302,21 +3302,17 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
* 'rowMarks' is a list of PlanRowMarks (non-locking only)
* 'onconflict' is the ON CONFLICT clause, or NULL
* 'epqParam' is the ID of Param for EvalPlanQual re-eval
- * 'mergeActionList' is a list of MERGE actions
*/
ModifyTablePath *
create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
CmdType operation, bool canSetTag,
Index nominalRelation, List *partitioned_rels,
bool partColsUpdated,
- List *resultRelations,
- Index mergeTargetRelation,
- List *subpaths,
+ List *resultRelations, List *subpaths,
List *subroots,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
- List *mergeSourceTargetList,
- List *mergeActionList, int epqParam)
+ int epqParam)
{
ModifyTablePath *pathnode = makeNode(ModifyTablePath);
double total_size;
@@ -3381,7 +3377,6 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->partitioned_rels = list_copy(partitioned_rels);
pathnode->partColsUpdated = partColsUpdated;
pathnode->resultRelations = resultRelations;
- pathnode->mergeTargetRelation = mergeTargetRelation;
pathnode->subpaths = subpaths;
pathnode->subroots = subroots;
pathnode->withCheckOptionLists = withCheckOptionLists;
@@ -3389,8 +3384,6 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->rowMarks = rowMarks;
pathnode->onconflict = onconflict;
pathnode->epqParam = epqParam;
- pathnode->mergeSourceTargetList = mergeSourceTargetList;
- pathnode->mergeActionList = mergeActionList;
return pathnode;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 90bb0c28041..04ee5f20153 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1851,10 +1851,6 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
trigDesc->trig_delete_before_row))
result = true;
break;
- /* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */
- case CMD_MERGE:
- result = false;
- break;
default:
elog(ERROR, "unrecognized CmdType: %d", (int) event);
break;
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 95fdf0b9732..f14febdbda0 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -14,7 +14,7 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS= analyze.o gram.o scan.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_collate.o parse_cte.o \
- parse_enr.o parse_expr.o parse_func.o parse_merge.o parse_node.o parse_oper.o \
+ parse_enr.o parse_expr.o parse_func.o parse_node.o parse_oper.o \
parse_param.o parse_relation.o parse_target.o parse_type.o \
parse_utilcmd.o scansup.o
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 606021bc94f..0c66ea1dfce 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -38,7 +38,6 @@
#include "parser/parse_cte.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
-#include "parser/parse_merge.h"
#include "parser/parse_oper.h"
#include "parser/parse_param.h"
#include "parser/parse_relation.h"
@@ -54,6 +53,9 @@ post_parse_analyze_hook_type post_parse_analyze_hook = NULL;
static Query *transformOptionalSelectInto(ParseState *pstate, Node *parseTree);
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
+static List *transformInsertRow(ParseState *pstate, List *exprlist,
+ List *stmtcols, List *icolumns, List *attrnos,
+ bool strip_indirection);
static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
OnConflictClause *onConflictClause);
static int count_rowexpr_columns(ParseState *pstate, Node *expr);
@@ -66,6 +68,8 @@ static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
+static List *transformUpdateTargetList(ParseState *pstate,
+ List *targetList);
static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate,
@@ -263,7 +267,6 @@ transformStmt(ParseState *pstate, Node *parseTree)
case T_InsertStmt:
case T_UpdateStmt:
case T_DeleteStmt:
- case T_MergeStmt:
(void) test_raw_expression_coverage(parseTree, NULL);
break;
default:
@@ -288,10 +291,6 @@ transformStmt(ParseState *pstate, Node *parseTree)
result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
break;
- case T_MergeStmt:
- result = transformMergeStmt(pstate, (MergeStmt *) parseTree);
- break;
-
case T_SelectStmt:
{
SelectStmt *n = (SelectStmt *) parseTree;
@@ -367,7 +366,6 @@ analyze_requires_snapshot(RawStmt *parseTree)
case T_InsertStmt:
case T_DeleteStmt:
case T_UpdateStmt:
- case T_MergeStmt:
case T_SelectStmt:
result = true;
break;
@@ -898,7 +896,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* attrnos: integer column numbers (must be same length as icolumns)
* strip_indirection: if true, remove any field/array assignment nodes
*/
-List *
+static List *
transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos,
bool strip_indirection)
@@ -2262,9 +2260,9 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
/*
* transformUpdateTargetList -
- * handle SET clause in UPDATE/MERGE/INSERT ... ON CONFLICT UPDATE
+ * handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE
*/
-List *
+static List *
transformUpdateTargetList(ParseState *pstate, List *origTlist)
{
List *tlist = NIL;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dd0c26c11b8..e5484766234 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -241,7 +241,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
RoleSpec *rolespec;
- MergeWhenClause *mergewhen;
}
%type <node> stmt schema_stmt
@@ -283,7 +282,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CreateMatViewStmt RefreshMatViewStmt CreateAmStmt
CreatePublicationStmt AlterPublicationStmt
CreateSubscriptionStmt AlterSubscriptionStmt DropSubscriptionStmt
- MergeStmt
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
@@ -402,7 +400,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
- merge_values_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -463,7 +460,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <istmt> insert_rest
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
-%type <mergewhen> merge_insert merge_update merge_delete
%type <vsetstmt> generic_set set_rest set_rest_more generic_reset reset_rest
SetResetClause FunctionSetResetClause
@@ -589,9 +585,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> hash_partbound partbound_datum_list range_datum_list
%type <defelt> hash_partbound_elem
-%type <node> merge_when_clause opt_merge_when_and_condition
-%type <list> merge_when_list
-
/*
* 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
@@ -659,8 +652,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
- MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
- MINUTE_P MINVALUE MODE MONTH_P MOVE
+ MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
@@ -929,7 +921,6 @@ stmt :
| RefreshMatViewStmt
| LoadStmt
| LockStmt
- | MergeStmt
| NotifyStmt
| PrepareStmt
| ReassignOwnedStmt
@@ -10689,7 +10680,6 @@ ExplainableStmt:
| InsertStmt
| UpdateStmt
| DeleteStmt
- | MergeStmt
| DeclareCursorStmt
| CreateAsStmt
| CreateMatViewStmt
@@ -10752,7 +10742,6 @@ PreparableStmt:
| InsertStmt
| UpdateStmt
| DeleteStmt /* by default all are $$=$1 */
- | MergeStmt
;
/*****************************************************************************
@@ -11122,142 +11111,6 @@ set_target_list:
/*****************************************************************************
*
* QUERY:
- * MERGE STATEMENTS
- *
- *****************************************************************************/
-
-MergeStmt:
- opt_with_clause MERGE INTO relation_expr_opt_alias
- USING table_ref
- ON a_expr
- merge_when_list
- {
- MergeStmt *m = makeNode(MergeStmt);
-
- m->withClause = $1;
- m->relation = $4;
- m->source_relation = $6;
- m->join_condition = $8;
- m->mergeWhenClauses = $9;
-
- $$ = (Node *)m;
- }
- ;
-
-
-merge_when_list:
- merge_when_clause { $$ = list_make1($1); }
- | merge_when_list merge_when_clause { $$ = lappend($1,$2); }
- ;
-
-merge_when_clause:
- WHEN MATCHED opt_merge_when_and_condition THEN merge_update
- {
- $5->matched = true;
- $5->commandType = CMD_UPDATE;
- $5->condition = $3;
-
- $$ = (Node *) $5;
- }
- | WHEN MATCHED opt_merge_when_and_condition THEN merge_delete
- {
- MergeWhenClause *m = makeNode(MergeWhenClause);
-
- m->matched = true;
- m->commandType = CMD_DELETE;
- m->condition = $3;
-
- $$ = (Node *)m;
- }
- | WHEN NOT MATCHED opt_merge_when_and_condition THEN merge_insert
- {
- $6->matched = false;
- $6->commandType = CMD_INSERT;
- $6->condition = $4;
-
- $$ = (Node *) $6;
- }
- | WHEN NOT MATCHED opt_merge_when_and_condition THEN DO NOTHING
- {
- MergeWhenClause *m = makeNode(MergeWhenClause);
-
- m->matched = false;
- m->commandType = CMD_NOTHING;
- m->condition = $4;
-
- $$ = (Node *)m;
- }
- ;
-
-opt_merge_when_and_condition:
- AND a_expr { $$ = $2; }
- | { $$ = NULL; }
- ;
-
-merge_delete:
- DELETE_P { $$ = NULL; }
- ;
-
-merge_update:
- UPDATE SET set_clause_list
- {
- MergeWhenClause *n = makeNode(MergeWhenClause);
- n->targetList = $3;
-
- $$ = n;
- }
- ;
-
-merge_insert:
- INSERT merge_values_clause
- {
- MergeWhenClause *n = makeNode(MergeWhenClause);
- n->cols = NIL;
- n->values = $2;
- $$ = n;
- }
- | INSERT OVERRIDING override_kind VALUE_P merge_values_clause
- {
- MergeWhenClause *n = makeNode(MergeWhenClause);
- n->cols = NIL;
- n->override = $3;
- n->values = $5;
- $$ = n;
- }
- | INSERT '(' insert_column_list ')' merge_values_clause
- {
- MergeWhenClause *n = makeNode(MergeWhenClause);
- n->cols = $3;
- n->values = $5;
- $$ = n;
- }
- | INSERT '(' insert_column_list ')' OVERRIDING override_kind VALUE_P merge_values_clause
- {
- MergeWhenClause *n = makeNode(MergeWhenClause);
- n->cols = $3;
- n->override = $6;
- n->values = $8;
- $$ = n;
- }
- | INSERT DEFAULT VALUES
- {
- MergeWhenClause *n = makeNode(MergeWhenClause);
- n->cols = NIL;
- n->values = NIL;
- $$ = n;
- }
- ;
-
-merge_values_clause:
- VALUES '(' expr_list ')'
- {
- $$ = $3;
- }
- ;
-
-/*****************************************************************************
- *
- * QUERY:
* CURSOR STATEMENTS
*
*****************************************************************************/
@@ -15256,10 +15109,8 @@ unreserved_keyword:
| LOGGED
| MAPPING
| MATCH
- | MATCHED
| MATERIALIZED
| MAXVALUE
- | MERGE
| METHOD
| MINUTE_P
| MINVALUE
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 0307738946a..61727e1d71a 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -455,13 +455,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
case EXPR_KIND_VALUES_SINGLE:
errkind = true;
break;
- case EXPR_KIND_MERGE_WHEN_AND:
- if (isAgg)
- err = _("aggregate functions are not allowed in WHEN AND conditions");
- else
- err = _("grouping operations are not allowed in WHEN AND conditions");
-
- break;
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
if (isAgg)
@@ -880,9 +873,6 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_VALUES_SINGLE:
errkind = true;
break;
- case EXPR_KIND_MERGE_WHEN_AND:
- err = _("window functions are not allowed in WHEN AND conditions");
- break;
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
err = _("window functions are not allowed in check constraints");
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c73d06b3917..e1478805c26 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -76,6 +76,9 @@ static RangeTblEntry *transformRangeTableFunc(ParseState *pstate,
RangeTableFunc *t);
static TableSampleClause *transformRangeTableSample(ParseState *pstate,
RangeTableSample *rts);
+static Node *transformFromClauseItem(ParseState *pstate, Node *n,
+ RangeTblEntry **top_rte, int *top_rti,
+ List **namespace);
static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
Var *l_colvar, Var *r_colvar);
static ParseNamespaceItem *makeNamespaceItem(RangeTblEntry *rte,
@@ -136,7 +139,6 @@ transformFromClause(ParseState *pstate, List *frmList)
n = transformFromClauseItem(pstate, n,
&rte,
&rtindex,
- NULL, NULL,
&namespace);
checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace);
@@ -1094,21 +1096,14 @@ getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
*
* *top_rti: receives the rangetable index of top_rte. (Ditto.)
*
- * *right_rte: receives the RTE corresponding to the right side of the
- * jointree. Only MERGE really needs to know about this and only MERGE passes a
- * non-NULL pointer.
- *
- * *right_rti: receives the rangetable index of the right_rte.
- *
* *namespace: receives a List of ParseNamespaceItems for the RTEs exposed
* as table/column names by this item. (The lateral_only flags in these items
* are indeterminate and should be explicitly set by the caller before use.)
*/
-Node *
+static Node *
transformFromClauseItem(ParseState *pstate, Node *n,
RangeTblEntry **top_rte, int *top_rti,
- RangeTblEntry **right_rte, int *right_rti,
- List **fnamespace)
+ List **namespace)
{
if (IsA(n, RangeVar))
{
@@ -1130,7 +1125,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
*top_rte = rte;
*top_rti = rtindex;
- *fnamespace = list_make1(makeDefaultNSItem(rte));
+ *namespace = list_make1(makeDefaultNSItem(rte));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -1148,7 +1143,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
*top_rte = rte;
*top_rti = rtindex;
- *fnamespace = list_make1(makeDefaultNSItem(rte));
+ *namespace = list_make1(makeDefaultNSItem(rte));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -1166,7 +1161,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
*top_rte = rte;
*top_rti = rtindex;
- *fnamespace = list_make1(makeDefaultNSItem(rte));
+ *namespace = list_make1(makeDefaultNSItem(rte));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -1184,7 +1179,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
*top_rte = rte;
*top_rti = rtindex;
- *fnamespace = list_make1(makeDefaultNSItem(rte));
+ *namespace = list_make1(makeDefaultNSItem(rte));
rtr = makeNode(RangeTblRef);
rtr->rtindex = rtindex;
return (Node *) rtr;
@@ -1199,7 +1194,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
/* Recursively transform the contained relation */
rel = transformFromClauseItem(pstate, rts->relation,
- top_rte, top_rti, NULL, NULL, fnamespace);
+ top_rte, top_rti, namespace);
/* Currently, grammar could only return a RangeVar as contained rel */
rtr = castNode(RangeTblRef, rel);
rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
@@ -1227,7 +1222,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
List *l_namespace,
*r_namespace,
*my_namespace,
- *save_namespace,
*l_colnames,
*r_colnames,
*res_colnames,
@@ -1246,7 +1240,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
j->larg = transformFromClauseItem(pstate, j->larg,
&l_rte,
&l_rtindex,
- NULL, NULL,
&l_namespace);
/*
@@ -1270,34 +1263,12 @@ transformFromClauseItem(ParseState *pstate, Node *n,
sv_namespace_length = list_length(pstate->p_namespace);
pstate->p_namespace = list_concat(pstate->p_namespace, l_namespace);
- /*
- * If we are running MERGE, don't make the other RTEs visible while
- * parsing the source relation. It mustn't see them.
- *
- * Currently, only MERGE passes non-NULL value for right_rte, so we
- * can safely deduce if we're running MERGE or not by just looking at
- * the right_rte. If that ever changes, we should look at other means
- * to find that.
- */
- if (right_rte)
- {
- save_namespace = pstate->p_namespace;
- pstate->p_namespace = NIL;
- }
-
/* And now we can process the RHS */
j->rarg = transformFromClauseItem(pstate, j->rarg,
&r_rte,
&r_rtindex,
- NULL, NULL,
&r_namespace);
- /*
- * And now restore the namespace again so that join-quals can see it.
- */
- if (right_rte)
- pstate->p_namespace = save_namespace;
-
/* Remove the left-side RTEs from the namespace list again */
pstate->p_namespace = list_truncate(pstate->p_namespace,
sv_namespace_length);
@@ -1324,12 +1295,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
expandRTE(r_rte, r_rtindex, 0, -1, false,
&r_colnames, &r_colvars);
- if (right_rte)
- *right_rte = r_rte;
-
- if (right_rti)
- *right_rti = r_rtindex;
-
/*
* Natural join does not explicitly specify columns; must generate
* columns to join. Need to run through the list of columns from each
@@ -1558,7 +1523,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
* The join RTE itself is always made visible for unqualified column
* names. It's visible as a relation name only if it has an alias.
*/
- *fnamespace = lappend(my_namespace,
+ *namespace = lappend(my_namespace,
makeNamespaceItem(rte,
(j->alias != NULL),
true,
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 51c73c4018a..6d34245083e 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -485,7 +485,6 @@ assign_collations_walker(Node *node, assign_collations_context *context)
case T_FromExpr:
case T_OnConflictExpr:
case T_SortGroupClause:
- case T_MergeAction:
(void) expression_tree_walker(node,
assign_collations_walker,
(void *) &loccontext);
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 38fbe3366fc..385e54a9b69 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1818,7 +1818,6 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
case EXPR_KIND_VALUES_SINGLE:
- case EXPR_KIND_MERGE_WHEN_AND:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3476,8 +3475,6 @@ ParseExprKindName(ParseExprKind exprKind)
return "PARTITION BY";
case EXPR_KIND_CALL_ARGUMENT:
return "CALL";
- case EXPR_KIND_MERGE_WHEN_AND:
- return "MERGE WHEN AND";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 615aee6d15f..ea5d5212b4c 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2277,9 +2277,6 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
/* okay, since we process this like a SELECT tlist */
pstate->p_hasTargetSRFs = true;
break;
- case EXPR_KIND_MERGE_WHEN_AND:
- err = _("set-returning functions are not allowed in WHEN AND conditions");
- break;
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
err = _("set-returning functions are not allowed in check constraints");
diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c
deleted file mode 100644
index 722cb23b86c..00000000000
--- a/src/backend/parser/parse_merge.c
+++ /dev/null
@@ -1,654 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * parse_merge.c
- * handle merge-statement in parser
- *
- * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- * src/backend/parser/parse_merge.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include "miscadmin.h"
-
-#include "access/sysattr.h"
-#include "nodes/makefuncs.h"
-#include "parser/analyze.h"
-#include "parser/parse_collate.h"
-#include "parser/parsetree.h"
-#include "parser/parser.h"
-#include "parser/parse_clause.h"
-#include "parser/parse_cte.h"
-#include "parser/parse_merge.h"
-#include "parser/parse_relation.h"
-#include "parser/parse_target.h"
-#include "utils/rel.h"
-#include "utils/relcache.h"
-
-static int transformMergeJoinClause(ParseState *pstate, Node *merge,
- List **mergeSourceTargetList);
-static void setNamespaceForMergeWhen(ParseState *pstate,
- MergeWhenClause *mergeWhenClause);
-static void setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
- bool rel_visible,
- bool cols_visible);
-static List *expandSourceTL(ParseState *pstate, RangeTblEntry *rte,
- int rtindex);
-
-/*
- * Special handling for MERGE statement is required because we assemble
- * the query manually. This is similar to setTargetTable() followed
- * by transformFromClause() but with a few less steps.
- *
- * Process the FROM clause and add items to the query's range table,
- * joinlist, and namespace.
- *
- * A special targetlist comprising of the columns from the right-subtree of
- * the join is populated and returned. Note that when the JoinExpr is
- * setup by transformMergeStmt, the left subtree has the target result
- * relation and the right subtree has the source relation.
- *
- * Returns the rangetable index of the target relation.
- */
-static int
-transformMergeJoinClause(ParseState *pstate, Node *merge,
- List **mergeSourceTargetList)
-{
- RangeTblEntry *rte,
- *rt_rte;
- List *namespace;
- int rtindex,
- rt_rtindex;
- Node *n;
- int mergeTarget_relation = list_length(pstate->p_rtable) + 1;
- Var *var;
- TargetEntry *te;
-
- n = transformFromClauseItem(pstate, merge,
- &rte,
- &rtindex,
- &rt_rte,
- &rt_rtindex,
- &namespace);
-
- pstate->p_joinlist = list_make1(n);
-
- /*
- * We created an internal join between the target and the source relation
- * to carry out the MERGE actions. Normally such an unaliased join hides
- * the joining relations, unless the column references are qualified.
- * Also, any unqualified column references are resolved to the Join RTE, if
- * there is a matching entry in the targetlist. But the way MERGE
- * execution is later setup, we expect all column references to resolve to
- * either the source or the target relation. Hence we must not add the
- * Join RTE to the namespace.
- *
- * The last entry must be for the top-level Join RTE. We don't want to
- * resolve any references to the Join RTE. So discard that.
- *
- * We also do not want to resolve any references from the leftside of the
- * Join since that corresponds to the target relation. References to the
- * columns of the target relation must be resolved from the result
- * relation and not the one that is used in the join. So the
- * mergeTarget_relation is marked invisible to both qualified as well as
- * unqualified references.
- */
- Assert(list_length(namespace) > 1);
- namespace = list_truncate(namespace, list_length(namespace) - 1);
- pstate->p_namespace = list_concat(pstate->p_namespace, namespace);
-
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- rt_fetch(mergeTarget_relation, pstate->p_rtable), false, false);
-
- /*
- * Expand the right relation and add its columns to the
- * mergeSourceTargetList. Note that the right relation can either be a
- * plain relation or a subquery or anything that can have a
- * RangeTableEntry.
- */
- *mergeSourceTargetList = expandSourceTL(pstate, rt_rte, rt_rtindex);
-
- /*
- * Add a whole-row-Var entry to support references to "source.*".
- */
- var = makeWholeRowVar(rt_rte, rt_rtindex, 0, false);
- te = makeTargetEntry((Expr *) var, list_length(*mergeSourceTargetList) + 1,
- NULL, true);
- *mergeSourceTargetList = lappend(*mergeSourceTargetList, te);
-
- return mergeTarget_relation;
-}
-
-/*
- * Make appropriate changes to the namespace visibility while transforming
- * individual action's quals and targetlist expressions. In particular, for
- * INSERT actions we must only see the source relation (since INSERT action is
- * invoked for NOT MATCHED tuples and hence there is no target tuple to deal
- * with). On the other hand, UPDATE and DELETE actions can see both source and
- * target relations.
- *
- * Also, since the internal Join node can hide the source and target
- * relations, we must explicitly make the respective relation as visible so
- * that columns can be referenced unqualified from these relations.
- */
-static void
-setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause)
-{
- RangeTblEntry *targetRelRTE,
- *sourceRelRTE;
-
- /* Assume target relation is at index 1 */
- targetRelRTE = rt_fetch(1, pstate->p_rtable);
-
- /*
- * Assume that the top-level join RTE is at the end. The source relation
- * is just before that.
- */
- sourceRelRTE = rt_fetch(list_length(pstate->p_rtable) - 1, pstate->p_rtable);
-
- switch (mergeWhenClause->commandType)
- {
- case CMD_INSERT:
-
- /*
- * Inserts can't see target relation, but they can see source
- * relation.
- */
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- targetRelRTE, false, false);
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- sourceRelRTE, true, true);
- break;
-
- case CMD_UPDATE:
- case CMD_DELETE:
-
- /*
- * Updates and deletes can see both target and source relations.
- */
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- targetRelRTE, true, true);
- setNamespaceVisibilityForRTE(pstate->p_namespace,
- sourceRelRTE, true, true);
- break;
-
- case CMD_NOTHING:
- break;
- default:
- elog(ERROR, "unknown action in MERGE WHEN clause");
- }
-}
-
-/*
- * transformMergeStmt -
- * transforms a MERGE statement
- */
-Query *
-transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
-{
- Query *qry = makeNode(Query);
- ListCell *l;
- AclMode targetPerms = ACL_NO_RIGHTS;
- bool is_terminal[2];
- JoinExpr *joinexpr;
- RangeTblEntry *resultRelRTE, *mergeRelRTE;
- List *mergeActionList;
-
- /* There can't be any outer WITH to worry about */
- Assert(pstate->p_ctenamespace == NIL);
-
- qry->commandType = CMD_MERGE;
- qry->hasRecursive = false;
-
- /* process the WITH clause independently of all else */
- if (stmt->withClause)
- {
- if (stmt->withClause->recursive)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("WITH RECURSIVE is not supported for MERGE statement")));
-
- qry->cteList = transformWithClause(pstate, stmt->withClause);
- qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
- }
-
- /*
- * Check WHEN clauses for permissions and sanity
- */
- is_terminal[0] = false;
- is_terminal[1] = false;
- foreach(l, stmt->mergeWhenClauses)
- {
- MergeWhenClause *mergeWhenClause = (MergeWhenClause *) lfirst(l);
- int when_type = (mergeWhenClause->matched ? 0 : 1);
-
- /*
- * Collect action types so we can check Target permissions
- */
- switch (mergeWhenClause->commandType)
- {
- case CMD_INSERT:
- targetPerms |= ACL_INSERT;
- break;
- case CMD_UPDATE:
- targetPerms |= ACL_UPDATE;
- break;
- case CMD_DELETE:
- targetPerms |= ACL_DELETE;
- break;
- case CMD_NOTHING:
- break;
- default:
- elog(ERROR, "unknown action in MERGE WHEN clause");
- }
-
- /*
- * Check for unreachable WHEN clauses
- */
- if (mergeWhenClause->condition == NULL)
- is_terminal[when_type] = true;
- else if (is_terminal[when_type])
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
- }
-
- /*
- * Construct a query of the form
- * SELECT relation.ctid --junk attribute
- * ,relation.tableoid --junk attribute
- * ,source_relation.<somecols>
- * ,relation.<somecols>
- * FROM relation RIGHT JOIN source_relation
- * ON join_condition; -- no WHERE clause - all conditions are applied in
- * executor
- *
- * stmt->relation is the target relation, given as a RangeVar
- * stmt->source_relation is a RangeVar or subquery
- *
- * We specify the join as a RIGHT JOIN as a simple way of forcing the
- * first (larg) RTE to refer to the target table.
- *
- * The MERGE query's join can be tuned in some cases, see below for these
- * special case tweaks.
- *
- * We set QSRC_PARSER to show query constructed in parse analysis
- *
- * Note that we have only one Query for a MERGE statement and the planner
- * is called only once. That query is executed once to produce our stream
- * of candidate change rows, so the query must contain all of the columns
- * required by each of the targetlist or conditions for each action.
- *
- * As top-level statements INSERT, UPDATE and DELETE have a Query, whereas
- * with MERGE the individual actions do not require separate planning,
- * only different handling in the executor. See nodeModifyTable handling
- * of commandType CMD_MERGE.
- *
- * A sub-query can include the Target, but otherwise the sub-query cannot
- * reference the outermost Target table at all.
- */
- qry->querySource = QSRC_PARSER;
-
- /*
- * Setup the target table. Unlike regular UPDATE/DELETE, we don't expand
- * inheritance for the target relation in case of MERGE.
- *
- * This special arrangement is required for handling partitioned tables
- * because we perform an JOIN between the target and the source relation to
- * identify the matching and not-matching rows. If we take the usual path
- * of expanding the target table's inheritance and create one subplan per
- * partition, then we we won't be able to correctly identify the matching
- * and not-matching rows since for a given source row, there may not be a
- * matching row in one partition, but it may exists in some other
- * partition. So we must first append all the qualifying rows from all the
- * partitions and then do the matching.
- *
- * Once a target row is returned by the underlying join, we find the
- * correct partition and setup required state to carry out UPDATE/DELETE.
- * All of this happens during execution.
- */
- qry->resultRelation = setTargetTable(pstate, stmt->relation,
- false, /* do not expand inheritance */
- true, targetPerms);
-
- /*
- * Create a JOIN between the target and the source relation.
- */
- joinexpr = makeNode(JoinExpr);
- joinexpr->isNatural = false;
- joinexpr->alias = NULL;
- joinexpr->usingClause = NIL;
- joinexpr->quals = stmt->join_condition;
- joinexpr->larg = (Node *) stmt->relation;
- joinexpr->rarg = (Node *) stmt->source_relation;
-
- /*
- * Simplify the MERGE query as much as possible
- *
- * These seem like things that could go into Optimizer, but they are
- * semantic simplifications rather than optimizations, per se.
- *
- * If there are no INSERT actions we won't be using the non-matching
- * candidate rows for anything, so no need for an outer join. We do still
- * need an inner join for UPDATE and DELETE actions.
- */
- if (targetPerms & ACL_INSERT)
- joinexpr->jointype = JOIN_RIGHT;
- else
- joinexpr->jointype = JOIN_INNER;
-
- /*
- * We use a special purpose transformation here because the normal
- * routines don't quite work right for the MERGE case.
- *
- * A special mergeSourceTargetList is setup by transformMergeJoinClause().
- * It refers to all the attributes provided by the source relation. This
- * is later used by set_plan_refs() to fix the UPDATE/INSERT target lists
- * to so that they can correctly fetch the attributes from the source
- * relation.
- *
- * The target relation when used in the underlying join, gets a new RTE
- * with rte->inh set to true. We remember this RTE (and later pass on to
- * the planner and executor) for two main reasons:
- *
- * 1. If we ever need to run EvalPlanQual while performing MERGE, we must
- * make the modified tuple available to the underlying join query, which is
- * using a different RTE from the resultRelation RTE.
- *
- * 2. rewriteTargetListMerge() requires the RTE of the underlying join in
- * order to add junk CTID and TABLEOID attributes.
- */
- qry->mergeTarget_relation = transformMergeJoinClause(pstate, (Node *) joinexpr,
- &qry->mergeSourceTargetList);
-
- /*
- * The target table referenced in the MERGE is looked up twice; once while
- * setting it up as the result relation and again when it's used in the
- * underlying the join query. In some rare situations, it may happen that
- * these lookups return different results, for example, if a new relation
- * with the same name gets created in a schema which is ahead in the
- * search_path, in between the two lookups.
- *
- * It's a very narrow case, but nevertheless we guard against it by simply
- * checking if the OIDs returned by the two lookups is the same. If not, we
- * just throw an error.
- */
- Assert(qry->resultRelation > 0);
- Assert(qry->mergeTarget_relation > 0);
-
- /* Fetch both the RTEs */
- resultRelRTE = rt_fetch(qry->resultRelation, pstate->p_rtable);
- mergeRelRTE = rt_fetch(qry->mergeTarget_relation, pstate->p_rtable);
-
- if (resultRelRTE->relid != mergeRelRTE->relid)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("relation referenced by MERGE statement has changed")));
-
- /*
- * This query should just provide the source relation columns. Later, in
- * preprocess_targetlist(), we shall also add "ctid" attribute of the
- * target relation to ensure that the target tuple can be fetched
- * correctly.
- */
- qry->targetList = qry->mergeSourceTargetList;
-
- /* qry has no WHERE clause so absent quals are shown as NULL */
- qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
- qry->rtable = pstate->p_rtable;
-
- /*
- * XXX MERGE is unsupported in various cases
- */
- if (!(pstate->p_target_relation->rd_rel->relkind == RELKIND_RELATION ||
- pstate->p_target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("MERGE is not supported for this relation type")));
-
- if (pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
- pstate->p_target_relation->rd_rel->relhassubclass)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("MERGE is not supported for relations with inheritance")));
-
- if (pstate->p_target_relation->rd_rel->relhasrules)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("MERGE is not supported for relations with rules")));
-
- /*
- * We now have a good query shape, so now look at the when conditions and
- * action targetlists.
- *
- * Overall, the MERGE Query's targetlist is NIL.
- *
- * Each individual action has its own targetlist that needs separate
- * transformation. These transforms don't do anything to the overall
- * targetlist, since that is only used for resjunk columns.
- *
- * We can reference any column in Target or Source, which is OK because
- * both of those already have RTEs. There is nothing like the EXCLUDED
- * pseudo-relation for INSERT ON CONFLICT.
- */
- mergeActionList = NIL;
- foreach(l, stmt->mergeWhenClauses)
- {
- MergeWhenClause *mergeWhenClause = (MergeWhenClause *) lfirst(l);
- MergeAction *action = makeNode(MergeAction);
-
- action->commandType = mergeWhenClause->commandType;
- action->matched = mergeWhenClause->matched;
-
- /*
- * Set namespace for the specific action. This must be done before
- * analyzing the WHEN quals and the action targetlisst.
- */
- setNamespaceForMergeWhen(pstate, mergeWhenClause);
-
- /*
- * Transform the when condition.
- *
- * Note that these quals are NOT added to the join quals; instead they
- * are evaluated separately during execution to decide which of the
- * WHEN MATCHED or WHEN NOT MATCHED actions to execute.
- */
- action->qual = transformWhereClause(pstate, mergeWhenClause->condition,
- EXPR_KIND_MERGE_WHEN_AND, "WHEN");
-
- /*
- * Transform target lists for each INSERT and UPDATE action stmt
- */
- switch (action->commandType)
- {
- case CMD_INSERT:
- {
- List *exprList = NIL;
- ListCell *lc;
- RangeTblEntry *rte;
- ListCell *icols;
- ListCell *attnos;
- List *icolumns;
- List *attrnos;
-
- pstate->p_is_insert = true;
-
- icolumns = checkInsertTargets(pstate,
- mergeWhenClause->cols,
- &attrnos);
- Assert(list_length(icolumns) == list_length(attrnos));
-
- action->override = mergeWhenClause->override;
-
- /*
- * Handle INSERT much like in transformInsertStmt
- */
- if (mergeWhenClause->values == NIL)
- {
- /*
- * We have INSERT ... DEFAULT VALUES. We can handle
- * this case by emitting an empty targetlist --- all
- * columns will be defaulted when the planner expands
- * the targetlist.
- */
- exprList = NIL;
- }
- else
- {
- /*
- * Process INSERT ... VALUES with a single VALUES
- * sublist. We treat this case separately for
- * efficiency. The sublist is just computed directly
- * as the Query's targetlist, with no VALUES RTE. So
- * it works just like a SELECT without any FROM.
- */
-
- /*
- * Do basic expression transformation (same as a ROW()
- * expr, but allow SetToDefault at top level)
- */
- exprList = transformExpressionList(pstate,
- mergeWhenClause->values,
- EXPR_KIND_VALUES_SINGLE,
- true);
-
- /* Prepare row for assignment to target table */
- exprList = transformInsertRow(pstate, exprList,
- mergeWhenClause->cols,
- icolumns, attrnos,
- false);
- }
-
- /*
- * Generate action's target list using the computed list
- * of expressions. Also, mark all the target columns as
- * needing insert permissions.
- */
- rte = pstate->p_target_rangetblentry;
- icols = list_head(icolumns);
- attnos = list_head(attrnos);
- foreach(lc, exprList)
- {
- Expr *expr = (Expr *) lfirst(lc);
- ResTarget *col;
- AttrNumber attr_num;
- TargetEntry *tle;
-
- col = lfirst_node(ResTarget, icols);
- attr_num = (AttrNumber) lfirst_int(attnos);
-
- tle = makeTargetEntry(expr,
- attr_num,
- col->name,
- false);
- action->targetList = lappend(action->targetList, tle);
-
- rte->insertedCols = bms_add_member(rte->insertedCols,
- attr_num - FirstLowInvalidHeapAttributeNumber);
-
- icols = lnext(icols);
- attnos = lnext(attnos);
- }
- }
- break;
- case CMD_UPDATE:
- {
- pstate->p_is_insert = false;
- action->targetList = transformUpdateTargetList(pstate,
- mergeWhenClause->targetList);
- }
- break;
- case CMD_DELETE:
- break;
-
- case CMD_NOTHING:
- action->targetList = NIL;
- break;
- default:
- elog(ERROR, "unknown action in MERGE WHEN clause");
- }
-
- mergeActionList = lappend(mergeActionList, action);
- }
-
- qry->mergeActionList = mergeActionList;
-
- /* XXX maybe later */
- qry->returningList = NULL;
-
- qry->hasTargetSRFs = false;
- qry->hasSubLinks = pstate->p_hasSubLinks;
-
- assign_query_collations(pstate, qry);
-
- return qry;
-}
-
-static void
-setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
- bool rel_visible,
- bool cols_visible)
-{
- ListCell *lc;
-
- foreach(lc, namespace)
- {
- ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
-
- if (nsitem->p_rte == rte)
- {
- nsitem->p_rel_visible = rel_visible;
- nsitem->p_cols_visible = cols_visible;
- break;
- }
- }
-
-}
-
-/*
- * Expand the source relation to include all attributes of this RTE.
- *
- * This function is very similar to expandRelAttrs except that we don't mark
- * columns for SELECT privileges. That will be decided later when we transform
- * the action targetlists and the WHEN quals for actual references to the
- * source relation.
- */
-static List *
-expandSourceTL(ParseState *pstate, RangeTblEntry *rte, int rtindex)
-{
- List *names,
- *vars;
- ListCell *name,
- *var;
- List *te_list = NIL;
-
- expandRTE(rte, rtindex, 0, -1, false, &names, &vars);
-
- /*
- * Require read access to the table.
- */
- rte->requiredPerms |= ACL_SELECT;
-
- forboth(name, names, var, vars)
- {
- char *label = strVal(lfirst(name));
- Var *varnode = (Var *) lfirst(var);
- TargetEntry *te;
-
- te = makeTargetEntry((Expr *) varnode,
- (AttrNumber) pstate->p_next_resno++,
- label,
- false);
- te_list = lappend(te_list, te);
- }
-
- Assert(name == NULL && var == NULL); /* lists not the same length? */
-
- return te_list;
-}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 8b912eeea31..bf5df26009a 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -728,16 +728,6 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, const char *colname,
colname),
parser_errposition(pstate, location)));
- /* In MERGE WHEN AND condition, no system column is allowed except tableOid or OID */
- if (pstate->p_expr_kind == EXPR_KIND_MERGE_WHEN_AND &&
- attnum < InvalidAttrNumber &&
- !(attnum == TableOidAttributeNumber || attnum == ObjectIdAttributeNumber))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("system column \"%s\" reference in WHEN AND condition is invalid",
- colname),
- parser_errposition(pstate, location)));
-
if (attnum != InvalidAttrNumber)
{
/* now check to see if column actually is defined */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index cb4bcd58d16..88140bc6877 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1377,57 +1377,6 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
}
}
-void
-rewriteTargetListMerge(Query *parsetree, Relation target_relation)
-{
- Var *var = NULL;
- const char *attrname;
- TargetEntry *tle;
-
- Assert(target_relation->rd_rel->relkind == RELKIND_RELATION ||
- target_relation->rd_rel->relkind == RELKIND_MATVIEW ||
- target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
-
- /*
- * Emit CTID so that executor can find the row to update or delete.
- */
- var = makeVar(parsetree->mergeTarget_relation,
- SelfItemPointerAttributeNumber,
- TIDOID,
- -1,
- InvalidOid,
- 0);
-
- attrname = "ctid";
- tle = makeTargetEntry((Expr *) var,
- list_length(parsetree->targetList) + 1,
- pstrdup(attrname),
- true);
-
- parsetree->targetList = lappend(parsetree->targetList, tle);
-
- /*
- * If we are dealing with partitioned table, then emit TABLEOID so that
- * executor can find the partition the row belongs to.
- */
- if (target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
- {
- var = makeVar(parsetree->mergeTarget_relation,
- TableOidAttributeNumber,
- OIDOID,
- -1,
- InvalidOid,
- 0);
-
- attrname = "tableoid";
- tle = makeTargetEntry((Expr *) var,
- list_length(parsetree->targetList) + 1,
- pstrdup(attrname),
- true);
-
- parsetree->targetList = lappend(parsetree->targetList, tle);
- }
-}
/*
* matchLocks -
@@ -3382,7 +3331,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
}
else if (event == CMD_UPDATE)
{
- Assert(parsetree->override == OVERRIDING_NOT_SET);
parsetree->targetList =
rewriteTargetListIU(parsetree->targetList,
parsetree->commandType,
@@ -3390,48 +3338,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
rt_entry_relation,
parsetree->resultRelation, NULL);
}
- else if (event == CMD_MERGE)
- {
- Assert(parsetree->override == OVERRIDING_NOT_SET);
-
- /*
- * Rewrite each action targetlist separately
- */
- foreach(lc1, parsetree->mergeActionList)
- {
- MergeAction *action = (MergeAction *) lfirst(lc1);
-
- switch (action->commandType)
- {
- case CMD_NOTHING:
- case CMD_DELETE: /* Nothing to do here */
- break;
- case CMD_UPDATE:
- action->targetList =
- rewriteTargetListIU(action->targetList,
- action->commandType,
- parsetree->override,
- rt_entry_relation,
- parsetree->resultRelation,
- NULL);
- break;
- case CMD_INSERT:
- {
- action->targetList =
- rewriteTargetListIU(action->targetList,
- action->commandType,
- action->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 */
@@ -3445,20 +3351,13 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
locks = matchLocks(event, rt_entry_relation->rd_rules,
result_relation, parsetree, &hasUpdate);
- /*
- * XXX MERGE doesn't support write rules because they would violate
- * the SQL Standard spec and would be unclear how they should work.
- */
- if (event == CMD_MERGE)
- product_queries = NIL;
- else
- product_queries = fireRules(parsetree,
- result_relation,
- event,
- locks,
- &instead,
- &returning,
- &qual_product);
+ product_queries = fireRules(parsetree,
+ result_relation,
+ event,
+ locks,
+ &instead,
+ &returning,
+ &qual_product);
/*
* If there were no INSTEAD rules, and the target relation is a view
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index 57e52b4f988..61ef396d8af 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -379,95 +379,6 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
}
}
- /*
- * FOR MERGE, we fetch policies for UPDATE, DELETE and INSERT (and ALL)
- * and set them up so that we can enforce the appropriate policy depending
- * on the final action we take.
- *
- * We don't fetch the SELECT policies since they are correctly applied to
- * the root->mergeTarget_relation. The target rows are selected after
- * joining the mergeTarget_relation and the source relation and hence it's
- * enough to apply SELECT policies to the mergeTarget_relation.
- *
- * We don't push the UPDATE/DELETE USING quals to the RTE because we don't
- * really want to apply them while scanning the relation since we don't
- * know whether we will be doing a UPDATE or a DELETE at the end. We apply
- * the respective policy once we decide the final action on the target
- * tuple.
- *
- * XXX We are setting up USING quals as WITH CHECK. If RLS prohibits
- * UPDATE/DELETE on the target row, we shall throw an error instead of
- * silently ignoring the row. This is different than how normal
- * UPDATE/DELETE works and more in line with INSERT ON CONFLICT DO UPDATE
- * handling.
- */
- if (commandType == CMD_MERGE)
- {
- List *merge_permissive_policies;
- List *merge_restrictive_policies;
-
- /*
- * Fetch the UPDATE policies and set them up to execute on the
- * existing target row before doing UPDATE.
- */
- get_policies_for_relation(rel, CMD_UPDATE, user_id,
- &merge_permissive_policies,
- &merge_restrictive_policies);
-
- /*
- * WCO_RLS_MERGE_UPDATE_CHECK is used to check UPDATE USING quals on
- * the existing target row.
- */
- add_with_check_options(rel, rt_index,
- WCO_RLS_MERGE_UPDATE_CHECK,
- merge_permissive_policies,
- merge_restrictive_policies,
- withCheckOptions,
- hasSubLinks,
- true);
-
- /*
- * Same with DELETE policies.
- */
- get_policies_for_relation(rel, CMD_DELETE, user_id,
- &merge_permissive_policies,
- &merge_restrictive_policies);
-
- add_with_check_options(rel, rt_index,
- WCO_RLS_MERGE_DELETE_CHECK,
- merge_permissive_policies,
- merge_restrictive_policies,
- withCheckOptions,
- hasSubLinks,
- true);
-
- /*
- * No special handling is required for INSERT policies. They will be
- * checked and enforced during ExecInsert(). But we must add them to
- * withCheckOptions.
- */
- get_policies_for_relation(rel, CMD_INSERT, user_id,
- &merge_permissive_policies,
- &merge_restrictive_policies);
-
- add_with_check_options(rel, rt_index,
- WCO_RLS_INSERT_CHECK,
- merge_permissive_policies,
- merge_restrictive_policies,
- withCheckOptions,
- hasSubLinks,
- false);
-
- /* Enforce the WITH CHECK clauses of the UPDATE policies */
- add_with_check_options(rel, rt_index,
- WCO_RLS_UPDATE_CHECK,
- merge_permissive_policies,
- merge_restrictive_policies,
- withCheckOptions,
- hasSubLinks,
- false);
- }
-
heap_close(rel, NoLock);
/*
@@ -527,14 +438,6 @@ get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id,
if (policy->polcmd == ACL_DELETE_CHR)
cmd_matches = true;
break;
- case CMD_MERGE:
-
- /*
- * We do not support a separate policy for MERGE command.
- * Instead it derives from the policies defined for other
- * commands.
- */
- break;
default:
elog(ERROR, "unrecognized policy command type %d",
(int) cmd);
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 50f852a4aa7..66cc5c35c68 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -193,11 +193,6 @@ ProcessQuery(PlannedStmt *plan,
"DELETE " UINT64_FORMAT,
queryDesc->estate->es_processed);
break;
- case CMD_MERGE:
- snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
- "MERGE " UINT64_FORMAT,
- queryDesc->estate->es_processed);
- break;
default:
strcpy(completionTag, "???");
break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4effadcb300..f8bcd5e2e2f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -110,7 +110,6 @@ CommandIsReadOnly(PlannedStmt *pstmt)
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
- case CMD_MERGE:
return false;
case CMD_UTILITY:
/* For now, treat all utility commands as read/write */
@@ -1833,8 +1832,6 @@ QueryReturnsTuples(Query *parsetree)
case CMD_SELECT:
/* returns tuples */
return true;
- case CMD_MERGE:
- return false;
case CMD_INSERT:
case CMD_UPDATE:
case CMD_DELETE:
@@ -2079,10 +2076,6 @@ CreateCommandTag(Node *parsetree)
tag = "UPDATE";
break;
- case T_MergeStmt:
- tag = "MERGE";
- break;
-
case T_SelectStmt:
tag = "SELECT";
break;
@@ -2826,9 +2819,6 @@ CreateCommandTag(Node *parsetree)
case CMD_DELETE:
tag = "DELETE";
break;
- case CMD_MERGE:
- tag = "MERGE";
- break;
case CMD_UTILITY:
tag = CreateCommandTag(stmt->utilityStmt);
break;
@@ -2889,9 +2879,6 @@ CreateCommandTag(Node *parsetree)
case CMD_DELETE:
tag = "DELETE";
break;
- case CMD_MERGE:
- tag = "MERGE";
- break;
case CMD_UTILITY:
tag = CreateCommandTag(stmt->utilityStmt);
break;
@@ -2940,7 +2927,6 @@ GetCommandLogLevel(Node *parsetree)
case T_InsertStmt:
case T_DeleteStmt:
case T_UpdateStmt:
- case T_MergeStmt:
lev = LOGSTMT_MOD;
break;
@@ -3380,7 +3366,6 @@ GetCommandLogLevel(Node *parsetree)
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
- case CMD_MERGE:
lev = LOGSTMT_MOD;
break;
@@ -3411,7 +3396,6 @@ GetCommandLogLevel(Node *parsetree)
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
- case CMD_MERGE:
lev = LOGSTMT_MOD;
break;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 061de8e6d46..264728212f6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -659,28 +659,6 @@ static const SchemaQuery Query_for_list_of_updatables = {
NULL
};
-/* Relations supporting MERGE */
-static const SchemaQuery Query_for_list_of_mergetargets = {
- /* min_server_version */
- 110000,
- /* catname */
- "pg_catalog.pg_class c",
- /* selcondition */
- "c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
- CppAsString2(RELKIND_PARTITIONED_TABLE) ") AND "
- "c.relhasrules = false AND "
- "(c.relhassubclass = false OR "
- " c.relkind = " CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
- /* viscondition */
- "pg_catalog.pg_table_is_visible(c.oid)",
- /* namespace */
- "c.relnamespace",
- /* result */
- "pg_catalog.quote_ident(c.relname)",
- /* qualresult */
- NULL
-};
-
static const SchemaQuery Query_for_list_of_relations = {
/* min_server_version */
0,
@@ -1627,7 +1605,7 @@ psql_completion(const char *text, int start, int end)
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
"FETCH", "GRANT", "IMPORT", "INSERT", "LISTEN", "LOAD", "LOCK",
- "MERGE", "MOVE", "NOTIFY", "PREPARE",
+ "MOVE", "NOTIFY", "PREPARE",
"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
"RESET", "REVOKE", "ROLLBACK",
"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
@@ -3021,15 +2999,14 @@ psql_completion(const char *text, int start, int end)
* Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
*/
else if (Matches1("EXPLAIN"))
- COMPLETE_WITH_LIST8("SELECT", "INSERT", "DELETE", "UPDATE", "MERGE",
- "DECLARE", "ANALYZE", "VERBOSE");
+ COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+ "ANALYZE", "VERBOSE");
else if (Matches2("EXPLAIN", "ANALYZE"))
- COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "MERGE",
- "DECLARE", "VERBOSE");
+ COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+ "VERBOSE");
else if (Matches2("EXPLAIN", "VERBOSE") ||
Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
- COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "MERGE",
- "DECLARE");
+ COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
/* FETCH && MOVE */
/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
@@ -3252,9 +3229,6 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_CONST("SCHEMA");
/* INSERT --- can be inside EXPLAIN, RULE, etc */
- /* Complete NOT MATCHED THEN INSERT */
- else if (TailMatches4("NOT", "MATCHED", "THEN", "INSERT"))
- COMPLETE_WITH_LIST2("VALUES", "(");
/* Complete INSERT with "INTO" */
else if (TailMatches1("INSERT"))
COMPLETE_WITH_CONST("INTO");
@@ -3326,55 +3300,6 @@ psql_completion(const char *text, int start, int end)
Matches5("LOCK", "TABLE", MatchAny, "IN", "SHARE"))
COMPLETE_WITH_LIST3("MODE", "ROW EXCLUSIVE MODE",
"UPDATE EXCLUSIVE MODE");
-/* MERGE --- can be inside EXPLAIN */
- else if (TailMatches1("MERGE"))
- COMPLETE_WITH_CONST("INTO");
- else if (TailMatches2("MERGE", "INTO"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_mergetargets, NULL);
- else if (TailMatches3("MERGE", "INTO", MatchAny))
- COMPLETE_WITH_LIST2("USING", "AS");
- else if (TailMatches4("MERGE", "INTO", MatchAny, "USING"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
- /* with [AS] alias */
- else if (TailMatches5("MERGE", "INTO", MatchAny, "AS", MatchAny))
- COMPLETE_WITH_CONST("USING");
- else if (TailMatches4("MERGE", "INTO", MatchAny, MatchAny))
- COMPLETE_WITH_CONST("USING");
- else if (TailMatches6("MERGE", "INTO", MatchAny, "AS", MatchAny, "USING"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
- else if (TailMatches5("MERGE", "INTO", MatchAny, MatchAny, "USING"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
- /* ON */
- else if (TailMatches5("MERGE", "INTO", MatchAny, "USING", MatchAny))
- COMPLETE_WITH_CONST("ON");
- else if (TailMatches8("INTO", MatchAny, "AS", MatchAny, "USING", MatchAny, "AS", MatchAny))
- COMPLETE_WITH_CONST("ON");
- else if (TailMatches6("INTO", MatchAny, MatchAny, "USING", MatchAny, MatchAny))
- COMPLETE_WITH_CONST("ON");
- /* ON condition */
- else if (TailMatches5("INTO", MatchAny, "USING", MatchAny, "ON"))
- COMPLETE_WITH_ATTR(prev4_wd, "");
- else if (TailMatches9("INTO", MatchAny, "AS", MatchAny, "USING", MatchAny, "AS", MatchAny, "ON"))
- COMPLETE_WITH_ATTR(prev8_wd, "");
- else if (TailMatches7("INTO", MatchAny, MatchAny, "USING", MatchAny, MatchAny, "ON"))
- COMPLETE_WITH_ATTR(prev6_wd, "");
- /* WHEN [NOT] MATCHED */
- else if (TailMatches4("USING", MatchAny, "ON", MatchAny))
- COMPLETE_WITH_LIST2("WHEN MATCHED", "WHEN NOT MATCHED");
- else if (TailMatches6("USING", MatchAny, "AS", MatchAny, "ON", MatchAny))
- COMPLETE_WITH_LIST2("WHEN MATCHED", "WHEN NOT MATCHED");
- else if (TailMatches5("USING", MatchAny, MatchAny, "ON", MatchAny))
- COMPLETE_WITH_LIST2("WHEN MATCHED", "WHEN NOT MATCHED");
- else if (TailMatches2("WHEN", "MATCHED"))
- COMPLETE_WITH_LIST2("THEN", "AND");
- else if (TailMatches3("WHEN", "NOT", "MATCHED"))
- COMPLETE_WITH_LIST2("THEN", "AND");
- else if (TailMatches3("WHEN", "MATCHED", "THEN"))
- COMPLETE_WITH_LIST2("UPDATE", "DELETE");
- else if (TailMatches4("WHEN", "NOT", "MATCHED", "THEN"))
- COMPLETE_WITH_LIST2("INSERT", "DO");
- else if (TailMatches5("WHEN", "NOT", "MATCHED", "THEN", "DO"))
- COMPLETE_WITH_CONST("NOTHING");
/* NOTIFY --- can be inside EXPLAIN, RULE, etc */
else if (TailMatches1("NOTIFY"))
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 7d756f20b08..ca5cad7497f 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -53,34 +53,23 @@ typedef enum LockTupleMode
* When heap_update, heap_delete, or heap_lock_tuple fail because the target
* tuple is already outdated, they fill in this struct to provide information
* to the caller about what happened.
- *
- * result is the result of HeapTupleSatisfiesUpdate, leading to the failure.
- * It's set to HeapTupleMayBeUpdated when there is no failure.
- *
* ctid is the target's ctid link: it is the same as the target's TID if the
* target was deleted, or the location of the replacement tuple if the target
* was updated.
- *
* xmax is the outdating transaction's XID. If the caller wants to visit the
* replacement tuple, it must check that this matches before believing the
* replacement is really a match.
- *
* cmax is the outdating command's CID, but only when the failure code is
* HeapTupleSelfUpdated (i.e., something in the current transaction outdated
* the tuple); otherwise cmax is zero. (We make this restriction because
* HeapTupleHeaderGetCmax doesn't work for tuples outdated in other
* transactions.)
- *
- * lockmode is only relevant for callers of heap_update() and is the mode which
- * the caller should use in case it needs to lock the updated tuple.
*/
typedef struct HeapUpdateFailureData
{
- HTSU_Result result;
ItemPointerData ctid;
TransactionId xmax;
CommandId cmax;
- LockTupleMode lockmode;
} HeapUpdateFailureData;
@@ -173,7 +162,7 @@ extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
HeapTuple newtup,
CommandId cid, Snapshot crosscheck, bool wait,
- HeapUpdateFailureData *hufd);
+ HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
bool follow_update,
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 1b79a803103..a5b8610fa22 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -206,8 +206,7 @@ extern bool ExecBRDeleteTriggers(EState *estate,
EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
- HeapTuple fdw_trigtuple,
- HeapUpdateFailureData *hufdp);
+ HeapTuple fdw_trigtuple);
extern void ExecARDeleteTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
@@ -226,8 +225,7 @@ extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
- TupleTableSlot *slot,
- HeapUpdateFailureData *hufdp);
+ TupleTableSlot *slot);
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
diff --git a/src/include/executor/execMerge.h b/src/include/executor/execMerge.h
deleted file mode 100644
index 5ea8c4e50a8..00000000000
--- a/src/include/executor/execMerge.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * execMerge.h
- *
- *
- * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/executor/execMerge.h
- *
- *-------------------------------------------------------------------------
- */
-#ifndef EXECMERGE_H
-#define EXECMERGE_H
-
-#include "nodes/execnodes.h"
-
-/* flags for mt_merge_subcommands */
-#define MERGE_INSERT 0x01
-#define MERGE_UPDATE 0x02
-#define MERGE_DELETE 0x04
-
-extern void ExecMerge(ModifyTableState *mtstate, EState *estate,
- TupleTableSlot *slot, JunkFilter *junkfilter,
- ResultRelInfo *resultRelInfo);
-
-extern void ExecInitMerge(ModifyTableState *mtstate,
- EState *estate,
- ResultRelInfo *resultRelInfo);
-
-#endif /* NODEMERGE_H */
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index b2daf24c41d..0c36c8be30c 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -186,7 +186,6 @@ extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
PartitionDispatch *pd,
TupleTableSlot *slot,
EState *estate);
-extern int ExecFindPartitionByOid(PartitionTupleRouting *proute, Oid partoid);
extern ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
ResultRelInfo *resultRelInfo,
PartitionTupleRouting *proute,
diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h
index 1bc7a88dbd6..6e3c71759b8 100644
--- a/src/include/executor/instrument.h
+++ b/src/include/executor/instrument.h
@@ -59,11 +59,8 @@ typedef struct Instrumentation
double ntuples; /* Total tuples produced */
double ntuples2; /* Secondary node-specific tuple counter */
double nloops; /* # of run cycles for this node */
- double nfiltered1; /* # tuples removed by scanqual or joinqual OR
- * # tuples inserted by MERGE */
- double nfiltered2; /* # tuples removed by "other" quals OR
- * # tuples updated by MERGE */
- double nfiltered3; /* # tuples deleted by MERGE */
+ double nfiltered1; /* # tuples removed by scanqual or joinqual */
+ double nfiltered2; /* # tuples removed by "other" quals */
BufferUsage bufusage; /* Total buffer usage */
} Instrumentation;
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 7e9ab3cb6b4..0d7e579e1cb 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -18,28 +18,5 @@
extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
extern void ExecEndModifyTable(ModifyTableState *node);
extern void ExecReScanModifyTable(ModifyTableState *node);
-extern TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
- EState *estate,
- struct PartitionTupleRouting *proute,
- ResultRelInfo *targetRelInfo,
- TupleTableSlot *slot);
-extern TupleTableSlot *ExecDelete(ModifyTableState *mtstate,
- ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *planSlot,
- EPQState *epqstate, EState *estate, bool *tupleDeleted,
- bool processReturning, HeapUpdateFailureData *hufdp,
- MergeActionState *actionState, bool canSetTag,
- bool changingPart);
-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);
-extern void ExecCheckPlanOutput(Relation resultRel, List *targetList);
#endif /* NODEMODIFYTABLE_H */
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 78410b9f772..e5bdaecc4e3 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -64,7 +64,6 @@ typedef struct _SPI_plan *SPIPlanPtr;
#define SPI_OK_REL_REGISTER 15
#define SPI_OK_REL_UNREGISTER 16
#define SPI_OK_TD_REGISTER 17
-#define SPI_OK_MERGE 18
#define SPI_OPT_NONATOMIC (1 << 0)
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c53e1132697..9fe0b790956 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -363,17 +363,8 @@ typedef struct JunkFilter
AttrNumber *jf_cleanMap;
TupleTableSlot *jf_resultSlot;
AttrNumber jf_junkAttNo;
- AttrNumber jf_otherJunkAttNo;
} JunkFilter;
-typedef struct MergeState
-{
- /* List of MERGE MATCHED action states */
- List *matchedActionStates;
- /* List of MERGE NOT MATCHED action states */
- List *notMatchedActionStates;
-} MergeState;
-
/*
* OnConflictSetState
*
@@ -470,38 +461,8 @@ typedef struct ResultRelInfo
/* true if ready for tuple routing */
bool ri_PartitionReadyForRouting;
-
- 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
*
@@ -1019,11 +980,6 @@ typedef struct PlanState
if (((PlanState *)(node))->instrument) \
((PlanState *)(node))->instrument->nfiltered2 += (delta); \
} while(0)
-#define InstrCountFiltered3(node, delta) \
- do { \
- if (((PlanState *)(node))->instrument) \
- ((PlanState *)(node))->instrument->nfiltered3 += (delta); \
- } while(0)
/*
* EPQState is state for executing an EvalPlanQual recheck on a candidate
@@ -1071,27 +1027,13 @@ typedef struct ProjectSetState
} 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
* ----------------
*/
typedef struct ModifyTableState
{
PlanState ps; /* its first field is NodeTag */
- CmdType operation; /* INSERT, UPDATE, DELETE or MERGE */
+ CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
bool mt_done; /* are we done? */
PlanState **mt_plans; /* subplans (one per target rel) */
@@ -1107,8 +1049,6 @@ typedef struct ModifyTableState
List *mt_excludedtlist; /* the excluded pseudo relation's tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection target */
- TupleTableSlot *mt_mergeproj; /* MERGE action projection target */
-
/* Tuple-routing support info */
struct PartitionTupleRouting *mt_partition_tuple_routing;
@@ -1120,9 +1060,6 @@ typedef struct ModifyTableState
/* Per plan map for tuple conversion from child to root */
TupleConversionMap **mt_per_subplan_tupconv_maps;
-
- /* Flags showing which subcommands are present INS/UPD/DEL/DO NOTHING */
- int mt_merge_subcommands;
} ModifyTableState;
/* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index defdbae5070..adb159a6dab 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -97,7 +97,6 @@ typedef enum NodeTag
T_PlanState,
T_ResultState,
T_ProjectSetState,
- T_MergeActionState,
T_ModifyTableState,
T_AppendState,
T_MergeAppendState,
@@ -272,7 +271,6 @@ typedef enum NodeTag
T_RollupData,
T_GroupingSetData,
T_StatisticExtInfo,
- T_MergeAction,
/*
* TAGS FOR MEMORY NODES (memnodes.h)
@@ -313,7 +311,6 @@ typedef enum NodeTag
T_InsertStmt,
T_DeleteStmt,
T_UpdateStmt,
- T_MergeStmt,
T_SelectStmt,
T_AlterTableStmt,
T_AlterTableCmd,
@@ -478,7 +475,6 @@ typedef enum NodeTag
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
- T_MergeWhenClause,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
@@ -664,8 +660,7 @@ typedef enum CmdType
CMD_SELECT, /* select stmt */
CMD_UPDATE, /* update stmt */
CMD_INSERT, /* insert stmt */
- CMD_DELETE, /* delete stmt */
- CMD_MERGE, /* merge stmt */
+ CMD_DELETE,
CMD_UTILITY, /* cmds like create, destroy, copy, vacuum,
* etc. */
CMD_NOTHING /* dummy command for instead nothing rules
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c8405386cf9..de020e68a41 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -38,7 +38,7 @@ typedef enum OverridingKind
typedef enum QuerySource
{
QSRC_ORIGINAL, /* original parsetree (explicit query) */
- QSRC_PARSER, /* added by parse analysis in MERGE */
+ QSRC_PARSER, /* added by parse analysis (now unused) */
QSRC_INSTEAD_RULE, /* added by unconditional INSTEAD rule */
QSRC_QUAL_INSTEAD_RULE, /* added by conditional INSTEAD rule */
QSRC_NON_INSTEAD_RULE /* added by non-INSTEAD rule */
@@ -107,7 +107,7 @@ typedef struct Query
{
NodeTag type;
- CmdType commandType; /* select|insert|update|delete|merge|utility */
+ CmdType commandType; /* select|insert|update|delete|utility */
QuerySource querySource; /* where did I come from? */
@@ -118,7 +118,7 @@ typedef struct Query
Node *utilityStmt; /* non-null if commandType == CMD_UTILITY */
int resultRelation; /* rtable index of target relation for
- * INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
+ * INSERT/UPDATE/DELETE; 0 for SELECT */
bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasWindowFuncs; /* has window functions in tlist */
@@ -169,9 +169,6 @@ typedef struct Query
List *withCheckOptions; /* a list of WithCheckOption's, which are
* only added during rewrite and therefore
* are not written out as part of Query. */
- int mergeTarget_relation;
- List *mergeSourceTargetList;
- List *mergeActionList; /* list of actions for MERGE (only) */
/*
* The following two fields identify the portion of the source text string
@@ -1131,9 +1128,7 @@ typedef enum WCOKind
WCO_VIEW_CHECK, /* WCO on an auto-updatable view */
WCO_RLS_INSERT_CHECK, /* RLS INSERT WITH CHECK policy */
WCO_RLS_UPDATE_CHECK, /* RLS UPDATE WITH CHECK policy */
- WCO_RLS_CONFLICT_CHECK, /* RLS ON CONFLICT DO UPDATE USING policy */
- WCO_RLS_MERGE_UPDATE_CHECK, /* RLS MERGE UPDATE USING policy */
- WCO_RLS_MERGE_DELETE_CHECK /* RLS MERGE DELETE USING policy */
+ WCO_RLS_CONFLICT_CHECK /* RLS ON CONFLICT DO UPDATE USING policy */
} WCOKind;
typedef struct WithCheckOption
@@ -1509,46 +1504,6 @@ typedef struct UpdateStmt
} 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 *mergeWhenClauses; /* list of MergeWhenClause(es) */
- WithClause *withClause; /* WITH clause */
-} MergeStmt;
-
-typedef struct MergeWhenClause
-{
- NodeTag type;
- bool matched; /* true=MATCHED, false=NOT MATCHED */
- CmdType commandType; /* INSERT/UPDATE/DELETE/DO NOTHING */
- Node *condition; /* WHEN AND conditions (raw parser) */
- List *targetList; /* INSERT/UPDATE targetlist */
- /* the following members are only useful for INSERT action */
- List *cols; /* optional: names of the target columns */
- List *values; /* VALUES to INSERT, or NULL */
- OverridingKind override; /* OVERRIDING clause */
-} MergeWhenClause;
-
-/*
- * WHEN [NOT] MATCHED THEN action info
- */
-typedef struct MergeAction
-{
- NodeTag type;
- bool matched; /* true=MATCHED, false=NOT MATCHED */
- OverridingKind override; /* OVERRIDING clause */
- Node *qual; /* transformed WHEN AND conditions */
- CmdType commandType; /* INSERT/UPDATE/DELETE/DO NOTHING */
- List *targetList; /* the target list (of ResTarget) */
-} MergeAction;
-
-/* ----------------------
* Select Statement
*
* A "simple" SELECT is represented in the output of gram.y by a single
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index c5c33cd3360..f2dda82e66a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -18,7 +18,6 @@
#include "lib/stringinfo.h"
#include "nodes/bitmapset.h"
#include "nodes/lockoptions.h"
-#include "nodes/parsenodes.h"
#include "nodes/primnodes.h"
@@ -43,7 +42,7 @@ typedef struct PlannedStmt
{
NodeTag type;
- CmdType commandType; /* select|insert|update|delete|merge|utility */
+ CmdType commandType; /* select|insert|update|delete|utility */
uint64 queryId; /* query identifier (copied from Query) */
@@ -217,14 +216,13 @@ typedef struct ProjectSet
typedef struct ModifyTable
{
Plan plan;
- CmdType operation; /* INSERT, UPDATE, DELETE or MERGE */
+ CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
/* RT indexes of non-leaf tables in a partition tree */
List *partitioned_rels;
bool partColsUpdated; /* some part key in hierarchy updated */
List *resultRelations; /* integer list of RT indexes */
- Index mergeTargetRelation; /* RT index of the merge target */
int resultRelIndex; /* index of first resultRel in plan's list */
int rootResultRelIndex; /* index of the partitioned table root */
List *plans; /* plan(s) producing source data */
@@ -240,8 +238,6 @@ typedef struct ModifyTable
Node *onConflictWhere; /* WHERE for ON CONFLICT UPDATE */
Index exclRelRTI; /* RTI of the EXCLUDED pseudo relation */
List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */
- List *mergeSourceTargetList;
- List *mergeActionList; /* actions for MERGE */
} ModifyTable;
/* ----------------
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 73a41c5475a..d6dbf15a4b4 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1684,7 +1684,7 @@ typedef struct LockRowsPath
} LockRowsPath;
/*
- * ModifyTablePath represents performing INSERT/UPDATE/DELETE/MERGE
+ * ModifyTablePath represents performing INSERT/UPDATE/DELETE modifications
*
* We represent most things that will be in the ModifyTable plan node
* literally, except we have child Path(s) not Plan(s). But analysis of the
@@ -1693,14 +1693,13 @@ typedef struct LockRowsPath
typedef struct ModifyTablePath
{
Path path;
- CmdType operation; /* INSERT, UPDATE, DELETE or MERGE */
+ CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
/* RT indexes of non-leaf tables in a partition tree */
List *partitioned_rels;
bool partColsUpdated; /* some part key in hierarchy updated */
List *resultRelations; /* integer list of RT indexes */
- Index mergeTargetRelation; /* RT index of merge target relation */
List *subpaths; /* Path(s) producing source data */
List *subroots; /* per-target-table PlannerInfos */
List *withCheckOptionLists; /* per-target-table WCO lists */
@@ -1708,8 +1707,6 @@ typedef struct ModifyTablePath
List *rowMarks; /* PlanRowMarks (non-locking only) */
OnConflictExpr *onconflict; /* ON CONFLICT clause, or NULL */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
- List *mergeSourceTargetList;
- List *mergeActionList; /* actions for MERGE */
} ModifyTablePath;
/*
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 4f65686d9be..e99ae36befb 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -241,14 +241,11 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
CmdType operation, bool canSetTag,
Index nominalRelation, List *partitioned_rels,
bool partColsUpdated,
- List *resultRelations,
- Index mergeTargetRelation,
- List *subpaths,
+ List *resultRelations, List *subpaths,
List *subroots,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
- List *mergeSourceTargetList,
- List *mergeActionList, int epqParam);
+ int epqParam);
extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
Node *limitOffset, Node *limitCount,
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index 41fb10666e5..687ae1b5b7c 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -32,11 +32,6 @@ extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
bool locked_from_parent,
bool resolve_unknowns);
-extern List *transformInsertRow(ParseState *pstate, List *exprlist,
- List *stmtcols, List *icolumns, List *attrnos,
- bool strip_indirection);
-extern List *transformUpdateTargetList(ParseState *pstate,
- List *targetList);
extern Query *transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree);
extern Query *transformStmt(ParseState *pstate, Node *parseTree);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 81f758afbf0..23db40147b8 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -245,10 +245,8 @@ PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD)
PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD)
PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
-PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD)
PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)
PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
-PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD)
PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD)
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index 4420e720708..2c0e0928628 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -20,10 +20,7 @@ extern void transformFromClause(ParseState *pstate, List *frmList);
extern int setTargetTable(ParseState *pstate, RangeVar *relation,
bool inh, bool alsoSource, AclMode requiredPerms);
extern bool interpretOidsOption(List *defList, bool allowOids);
-extern Node *transformFromClauseItem(ParseState *pstate, Node *n,
- RangeTblEntry **top_rte, int *top_rti,
- RangeTblEntry **right_rte, int *right_rti,
- List **fnamespace);
+
extern Node *transformWhereClause(ParseState *pstate, Node *clause,
ParseExprKind exprKind, const char *constructName);
extern Node *transformLimitClause(ParseState *pstate, Node *clause,
diff --git a/src/include/parser/parse_merge.h b/src/include/parser/parse_merge.h
deleted file mode 100644
index 0151809e09b..00000000000
--- a/src/include/parser/parse_merge.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * parse_merge.h
- * handle merge-stmt in parser
- *
- *
- * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/parser/parse_merge.h
- *
- *-------------------------------------------------------------------------
- */
-#ifndef PARSE_MERGE_H
-#define PARSE_MERGE_H
-
-#include "parser/parse_node.h"
-extern Query *transformMergeStmt(ParseState *pstate, MergeStmt *stmt);
-#endif
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 3fd2151ccbe..0230543810f 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -50,7 +50,6 @@ typedef enum ParseExprKind
EXPR_KIND_INSERT_TARGET, /* INSERT target list item */
EXPR_KIND_UPDATE_SOURCE, /* UPDATE assignment source item */
EXPR_KIND_UPDATE_TARGET, /* UPDATE assignment target item */
- EXPR_KIND_MERGE_WHEN_AND, /* MERGE WHEN ... AND condition */
EXPR_KIND_GROUP_BY, /* GROUP BY */
EXPR_KIND_ORDER_BY, /* ORDER BY */
EXPR_KIND_DISTINCT_ON, /* DISTINCT ON */
@@ -128,7 +127,7 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
* p_parent_cte: CommonTableExpr that immediately contains the current query,
* if any.
*
- * p_target_relation: target relation, if query is INSERT/UPDATE/DELETE/MERGE
+ * p_target_relation: target relation, if query is INSERT, UPDATE, or DELETE.
*
* p_target_rangetblentry: target relation's entry in the rtable list.
*
@@ -182,7 +181,7 @@ struct ParseState
List *p_ctenamespace; /* current namespace for common table exprs */
List *p_future_ctes; /* common table exprs not yet in namespace */
CommonTableExpr *p_parent_cte; /* this query's containing CTE */
- Relation p_target_relation; /* INSERT/UPDATE/DELETE/MERGE target rel */
+ Relation p_target_relation; /* INSERT/UPDATE/DELETE target rel */
RangeTblEntry *p_target_rangetblentry; /* target rel's RTE */
bool p_is_insert; /* process assignment like INSERT not UPDATE */
List *p_windowdefs; /* raw representations of window clauses */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 1ab5de39422..8128199fc31 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -25,7 +25,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
extern Node *build_column_default(Relation rel, int attrno);
extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
Relation target_relation);
-extern void rewriteTargetListMerge(Query *parsetree, Relation target_relation);
extern Query *get_view_query(Relation view);
extern const char *view_query_is_auto_updatable(Query *viewquery,
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index a432636322b..4c0114c514d 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -3055,9 +3055,9 @@ PQoidValue(const PGresult *res)
/*
* PQcmdTuples -
- * If the last command was INSERT/UPDATE/DELETE/MERGE/MOVE/FETCH/COPY,
- * return a string containing the number of inserted/affected tuples.
- * If not, return "".
+ * If the last command was INSERT/UPDATE/DELETE/MOVE/FETCH/COPY, return
+ * a string containing the number of inserted/affected tuples. If not,
+ * return "".
*
* XXX: this should probably return an int
*/
@@ -3084,8 +3084,7 @@ PQcmdTuples(PGresult *res)
strncmp(res->cmdStatus, "DELETE ", 7) == 0 ||
strncmp(res->cmdStatus, "UPDATE ", 7) == 0)
p = res->cmdStatus + 7;
- else if (strncmp(res->cmdStatus, "FETCH ", 6) == 0 ||
- strncmp(res->cmdStatus, "MERGE ", 6) == 0)
+ else if (strncmp(res->cmdStatus, "FETCH ", 6) == 0)
p = res->cmdStatus + 6;
else if (strncmp(res->cmdStatus, "MOVE ", 5) == 0 ||
strncmp(res->cmdStatus, "COPY ", 5) == 0)
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 4b6f51ac98d..99f167a0a8c 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3995,7 +3995,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
/*
* On the first call for this statement generate the plan, and detect
- * whether the statement is INSERT/UPDATE/DELETE/MERGE
+ * whether the statement is INSERT/UPDATE/DELETE
*/
if (expr->plan == NULL)
{
@@ -4016,7 +4016,6 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
{
if (q->commandType == CMD_INSERT ||
q->commandType == CMD_UPDATE ||
- q->commandType == CMD_MERGE ||
q->commandType == CMD_DELETE)
stmt->mod_stmt = true;
}
@@ -4074,7 +4073,6 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
case SPI_OK_INSERT_RETURNING:
case SPI_OK_UPDATE_RETURNING:
case SPI_OK_DELETE_RETURNING:
- case SPI_OK_MERGE:
Assert(stmt->mod_stmt);
exec_set_found(estate, (SPI_processed != 0));
break;
@@ -4252,7 +4250,6 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
case SPI_OK_INSERT_RETURNING:
case SPI_OK_UPDATE_RETURNING:
case SPI_OK_DELETE_RETURNING:
- case SPI_OK_MERGE:
case SPI_OK_UTILITY:
case SPI_OK_REWRITTEN:
break;
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index f9ba19cbdf0..b59869a534a 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -304,7 +304,6 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <keyword> K_LAST
%token <keyword> K_LOG
%token <keyword> K_LOOP
-%token <keyword> K_MERGE
%token <keyword> K_MESSAGE
%token <keyword> K_MESSAGE_TEXT
%token <keyword> K_MOVE
@@ -1952,10 +1951,6 @@ stmt_execsql : K_IMPORT
{
$$ = make_execsql_stmt(K_INSERT, @1);
}
- | K_MERGE
- {
- $$ = make_execsql_stmt(K_MERGE, @1);
- }
| T_WORD
{
int tok;
@@ -2502,7 +2497,6 @@ unreserved_keyword :
| K_IS
| K_LAST
| K_LOG
- | K_MERGE
| K_MESSAGE
| K_MESSAGE_TEXT
| K_MOVE
@@ -2966,8 +2960,6 @@ make_execsql_stmt(int firsttoken, int location)
{
if (prev_tok == K_INSERT)
continue; /* INSERT INTO is not an INTO-target */
- if (prev_tok == K_MERGE)
- continue; /* MERGE INTO is not an INTO-target */
if (firsttoken == K_IMPORT)
continue; /* IMPORT ... INTO is not an INTO-target */
if (have_into)
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index 256fc0a243b..fc4ba3054a0 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -138,7 +138,6 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("is", K_IS, UNRESERVED_KEYWORD)
PG_KEYWORD("last", K_LAST, UNRESERVED_KEYWORD)
PG_KEYWORD("log", K_LOG, UNRESERVED_KEYWORD)
- PG_KEYWORD("merge", K_MERGE, UNRESERVED_KEYWORD)
PG_KEYWORD("message", K_MESSAGE, UNRESERVED_KEYWORD)
PG_KEYWORD("message_text", K_MESSAGE_TEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("move", K_MOVE, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 8d30180d424..fe617791dfd 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -857,8 +857,8 @@ typedef struct PLpgSQL_stmt_execsql
PLpgSQL_stmt_type cmd_type;
int lineno;
PLpgSQL_expr *sqlstmt;
- bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE/MERGE?
- * Note mod_stmt is set when we plan the query */
+ bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE? Note:
+ * mod_stmt is set when we plan the query */
bool into; /* INTO supplied? */
bool strict; /* INTO STRICT flag */
PLpgSQL_variable *target; /* INTO target (record or row) */
diff --git a/src/test/isolation/expected/merge-delete.out b/src/test/isolation/expected/merge-delete.out
deleted file mode 100644
index 40e62901b70..00000000000
--- a/src/test/isolation/expected/merge-delete.out
+++ /dev/null
@@ -1,97 +0,0 @@
-Parsed test spec with 2 sessions
-
-starting permutation: delete c1 select2 c2
-step delete: DELETE FROM target t WHERE t.key = 1;
-step c1: COMMIT;
-step select2: SELECT * FROM target;
-key val
-
-step c2: COMMIT;
-
-starting permutation: merge_delete c1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
-step c1: COMMIT;
-step select2: SELECT * FROM target;
-key val
-
-step c2: COMMIT;
-
-starting permutation: delete c1 update1 select2 c2
-step delete: DELETE FROM target t WHERE t.key = 1;
-step c1: COMMIT;
-step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1;
-step select2: SELECT * FROM target;
-key val
-
-step c2: COMMIT;
-
-starting permutation: merge_delete c1 update1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
-step c1: COMMIT;
-step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1;
-step select2: SELECT * FROM target;
-key val
-
-step c2: COMMIT;
-
-starting permutation: delete c1 merge2 select2 c2
-step delete: DELETE FROM target t WHERE t.key = 1;
-step c1: COMMIT;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-step select2: SELECT * FROM target;
-key val
-
-1 merge2a
-step c2: COMMIT;
-
-starting permutation: merge_delete c1 merge2 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
-step c1: COMMIT;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-step select2: SELECT * FROM target;
-key val
-
-1 merge2a
-step c2: COMMIT;
-
-starting permutation: delete update1 c1 select2 c2
-step delete: DELETE FROM target t WHERE t.key = 1;
-step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; <waiting ...>
-step c1: COMMIT;
-step update1: <... completed>
-step select2: SELECT * FROM target;
-key val
-
-step c2: COMMIT;
-
-starting permutation: merge_delete update1 c1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
-step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; <waiting ...>
-step c1: COMMIT;
-step update1: <... completed>
-step select2: SELECT * FROM target;
-key val
-
-step c2: COMMIT;
-
-starting permutation: delete merge2 c1 select2 c2
-step delete: DELETE FROM target t WHERE t.key = 1;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
-step c1: COMMIT;
-step merge2: <... completed>
-step select2: SELECT * FROM target;
-key val
-
-1 merge2a
-step c2: COMMIT;
-
-starting permutation: merge_delete merge2 c1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
-step c1: COMMIT;
-step merge2: <... completed>
-step select2: SELECT * FROM target;
-key val
-
-1 merge2a
-step c2: COMMIT;
diff --git a/src/test/isolation/expected/merge-insert-update.out b/src/test/isolation/expected/merge-insert-update.out
deleted file mode 100644
index 317fa16a3d5..00000000000
--- a/src/test/isolation/expected/merge-insert-update.out
+++ /dev/null
@@ -1,84 +0,0 @@
-Parsed test spec with 2 sessions
-
-starting permutation: merge1 c1 select2 c2
-step merge1: MERGE INTO target t USING (SELECT 1 as key, 'merge1' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge1';
-step c1: COMMIT;
-step select2: SELECT * FROM target;
-key val
-
-1 merge1
-step c2: COMMIT;
-
-starting permutation: merge1 c1 merge2 select2 c2
-step merge1: MERGE INTO target t USING (SELECT 1 as key, 'merge1' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge1';
-step c1: COMMIT;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge2';
-step select2: SELECT * FROM target;
-key val
-
-1 merge1 updated by merge2
-step c2: COMMIT;
-
-starting permutation: insert1 merge2 c1 select2 c2
-step insert1: INSERT INTO target VALUES (1, 'insert1');
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge2'; <waiting ...>
-step c1: COMMIT;
-step merge2: <... completed>
-error in steps c1 merge2: ERROR: duplicate key value violates unique constraint "target_pkey"
-step select2: SELECT * FROM target;
-ERROR: current transaction is aborted, commands ignored until end of transaction block
-step c2: COMMIT;
-
-starting permutation: merge1 merge2 c1 select2 c2
-step merge1: MERGE INTO target t USING (SELECT 1 as key, 'merge1' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge1';
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge2'; <waiting ...>
-step c1: COMMIT;
-step merge2: <... completed>
-error in steps c1 merge2: ERROR: duplicate key value violates unique constraint "target_pkey"
-step select2: SELECT * FROM target;
-ERROR: current transaction is aborted, commands ignored until end of transaction block
-step c2: COMMIT;
-
-starting permutation: merge1 merge2 a1 select2 c2
-step merge1: MERGE INTO target t USING (SELECT 1 as key, 'merge1' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge1';
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge2'; <waiting ...>
-step a1: ABORT;
-step merge2: <... completed>
-step select2: SELECT * FROM target;
-key val
-
-1 merge2
-step c2: COMMIT;
-
-starting permutation: delete1 insert1 c1 merge2 select2 c2
-step delete1: DELETE FROM target WHERE key = 1;
-step insert1: INSERT INTO target VALUES (1, 'insert1');
-step c1: COMMIT;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge2';
-step select2: SELECT * FROM target;
-key val
-
-1 insert1 updated by merge2
-step c2: COMMIT;
-
-starting permutation: delete1 insert1 merge2 c1 select2 c2
-step delete1: DELETE FROM target WHERE key = 1;
-step insert1: INSERT INTO target VALUES (1, 'insert1');
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge2'; <waiting ...>
-step c1: COMMIT;
-step merge2: <... completed>
-error in steps c1 merge2: ERROR: duplicate key value violates unique constraint "target_pkey"
-step select2: SELECT * FROM target;
-ERROR: current transaction is aborted, commands ignored until end of transaction block
-step c2: COMMIT;
-
-starting permutation: delete1 insert1 merge2i c1 select2 c2
-step delete1: DELETE FROM target WHERE key = 1;
-step insert1: INSERT INTO target VALUES (1, 'insert1');
-step merge2i: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge2';
-step c1: COMMIT;
-step select2: SELECT * FROM target;
-key val
-
-1 insert1
-step c2: COMMIT;
diff --git a/src/test/isolation/expected/merge-match-recheck.out b/src/test/isolation/expected/merge-match-recheck.out
deleted file mode 100644
index 96a9f45ac88..00000000000
--- a/src/test/isolation/expected/merge-match-recheck.out
+++ /dev/null
@@ -1,106 +0,0 @@
-Parsed test spec with 2 sessions
-
-starting permutation: update1 merge_status c2 select1 c1
-step update1: UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1;
-step merge_status:
- MERGE INTO target t
- USING (SELECT 1 as key) s
- ON s.key = t.key
- WHEN MATCHED AND status = 's1' THEN
- UPDATE SET status = 's2', val = t.val || ' when1'
- WHEN MATCHED AND status = 's2' THEN
- UPDATE SET status = 's3', val = t.val || ' when2'
- WHEN MATCHED AND status = 's3' THEN
- UPDATE SET status = 's4', val = t.val || ' when3';
- <waiting ...>
-step c2: COMMIT;
-step merge_status: <... completed>
-step select1: SELECT * FROM target;
-key balance status val
-
-1 170 s2 setup updated by update1 when1
-step c1: COMMIT;
-
-starting permutation: update2 merge_status c2 select1 c1
-step update2: UPDATE target t SET status = 's2', val = t.val || ' updated by update2' WHERE t.key = 1;
-step merge_status:
- MERGE INTO target t
- USING (SELECT 1 as key) s
- ON s.key = t.key
- WHEN MATCHED AND status = 's1' THEN
- UPDATE SET status = 's2', val = t.val || ' when1'
- WHEN MATCHED AND status = 's2' THEN
- UPDATE SET status = 's3', val = t.val || ' when2'
- WHEN MATCHED AND status = 's3' THEN
- UPDATE SET status = 's4', val = t.val || ' when3';
- <waiting ...>
-step c2: COMMIT;
-step merge_status: <... completed>
-step select1: SELECT * FROM target;
-key balance status val
-
-1 160 s3 setup updated by update2 when2
-step c1: COMMIT;
-
-starting permutation: update3 merge_status c2 select1 c1
-step update3: UPDATE target t SET status = 's3', val = t.val || ' updated by update3' WHERE t.key = 1;
-step merge_status:
- MERGE INTO target t
- USING (SELECT 1 as key) s
- ON s.key = t.key
- WHEN MATCHED AND status = 's1' THEN
- UPDATE SET status = 's2', val = t.val || ' when1'
- WHEN MATCHED AND status = 's2' THEN
- UPDATE SET status = 's3', val = t.val || ' when2'
- WHEN MATCHED AND status = 's3' THEN
- UPDATE SET status = 's4', val = t.val || ' when3';
- <waiting ...>
-step c2: COMMIT;
-step merge_status: <... completed>
-step select1: SELECT * FROM target;
-key balance status val
-
-1 160 s4 setup updated by update3 when3
-step c1: COMMIT;
-
-starting permutation: update5 merge_status c2 select1 c1
-step update5: UPDATE target t SET status = 's5', val = t.val || ' updated by update5' WHERE t.key = 1;
-step merge_status:
- MERGE INTO target t
- USING (SELECT 1 as key) s
- ON s.key = t.key
- WHEN MATCHED AND status = 's1' THEN
- UPDATE SET status = 's2', val = t.val || ' when1'
- WHEN MATCHED AND status = 's2' THEN
- UPDATE SET status = 's3', val = t.val || ' when2'
- WHEN MATCHED AND status = 's3' THEN
- UPDATE SET status = 's4', val = t.val || ' when3';
- <waiting ...>
-step c2: COMMIT;
-step merge_status: <... completed>
-step select1: SELECT * FROM target;
-key balance status val
-
-1 160 s5 setup updated by update5
-step c1: COMMIT;
-
-starting permutation: update_bal1 merge_bal c2 select1 c1
-step update_bal1: UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1;
-step merge_bal:
- MERGE INTO target t
- USING (SELECT 1 as key) s
- ON s.key = t.key
- WHEN MATCHED AND balance < 100 THEN
- UPDATE SET balance = balance * 2, val = t.val || ' when1'
- WHEN MATCHED AND balance < 200 THEN
- UPDATE SET balance = balance * 4, val = t.val || ' when2'
- WHEN MATCHED AND balance < 300 THEN
- UPDATE SET balance = balance * 8, val = t.val || ' when3';
- <waiting ...>
-step c2: COMMIT;
-step merge_bal: <... completed>
-step select1: SELECT * FROM target;
-key balance status val
-
-1 100 s1 setup updated by update_bal1 when1
-step c1: COMMIT;
diff --git a/src/test/isolation/expected/merge-update.out b/src/test/isolation/expected/merge-update.out
deleted file mode 100644
index 00069a3e459..00000000000
--- a/src/test/isolation/expected/merge-update.out
+++ /dev/null
@@ -1,238 +0,0 @@
-Parsed test spec with 2 sessions
-
-starting permutation: merge1 c1 select2 c2
-step merge1:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step c1: COMMIT;
-step select2: SELECT * FROM target;
-key val
-
-2 setup1 updated by merge1
-step c2: COMMIT;
-
-starting permutation: merge1 c1 merge2a select2 c2
-step merge1:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step c1: COMMIT;
-step merge2a:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge2a' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step select2: SELECT * FROM target;
-key val
-
-2 setup1 updated by merge1
-1 merge2a
-step c2: COMMIT;
-
-starting permutation: merge1 merge2a c1 select2 c2
-step merge1:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step merge2a:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge2a' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
- <waiting ...>
-step c1: COMMIT;
-step merge2a: <... completed>
-step select2: SELECT * FROM target;
-key val
-
-2 setup1 updated by merge1
-1 merge2a
-step c2: COMMIT;
-
-starting permutation: merge1 merge2a a1 select2 c2
-step merge1:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step merge2a:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge2a' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
- <waiting ...>
-step a1: ABORT;
-step merge2a: <... completed>
-step select2: SELECT * FROM target;
-key val
-
-2 setup1 updated by merge2a
-step c2: COMMIT;
-
-starting permutation: merge1 merge2b c1 select2 c2
-step merge1:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step merge2b:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge2b' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED AND t.key < 2 THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
- <waiting ...>
-step c1: COMMIT;
-step merge2b: <... completed>
-step select2: SELECT * FROM target;
-key val
-
-2 setup1 updated by merge1
-1 merge2b
-step c2: COMMIT;
-
-starting permutation: merge1 merge2c c1 select2 c2
-step merge1:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step merge2c:
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge2c' as val) s
- ON s.key = t.key AND t.key < 2
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
- <waiting ...>
-step c1: COMMIT;
-step merge2c: <... completed>
-step select2: SELECT * FROM target;
-key val
-
-2 setup1 updated by merge1
-1 merge2c
-step c2: COMMIT;
-
-starting permutation: pa_merge1 pa_merge2a c1 pa_select2 c2
-step pa_merge1:
- MERGE INTO pa_target t
- USING (SELECT 1 as key, 'pa_merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set val = t.val || ' updated by ' || s.val;
-
-step pa_merge2a:
- MERGE INTO pa_target t
- USING (SELECT 1 as key, 'pa_merge2a' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
- <waiting ...>
-step c1: COMMIT;
-step pa_merge2a: <... completed>
-step pa_select2: SELECT * FROM pa_target;
-key val
-
-2 initial
-2 initial updated by pa_merge2a
-step c2: COMMIT;
-
-starting permutation: pa_merge2 pa_merge2a c1 pa_select2 c2
-step pa_merge2:
- MERGE INTO pa_target t
- USING (SELECT 1 as key, 'pa_merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step pa_merge2a:
- MERGE INTO pa_target t
- USING (SELECT 1 as key, 'pa_merge2a' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
- <waiting ...>
-step c1: COMMIT;
-step pa_merge2a: <... completed>
-error in steps c1 pa_merge2a: ERROR: tuple to be deleted was already moved to another partition due to concurrent update
-step pa_select2: SELECT * FROM pa_target;
-ERROR: current transaction is aborted, commands ignored until end of transaction block
-step c2: COMMIT;
-
-starting permutation: pa_merge2 c1 pa_merge2a pa_select2 c2
-step pa_merge2:
- MERGE INTO pa_target t
- USING (SELECT 1 as key, 'pa_merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step c1: COMMIT;
-step pa_merge2a:
- MERGE INTO pa_target t
- USING (SELECT 1 as key, 'pa_merge2a' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-
-step pa_select2: SELECT * FROM pa_target;
-key val
-
-1 pa_merge2a
-2 initial
-2 initial updated by pa_merge1
-step c2: COMMIT;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 4abad7c15ee..b3a34a8688d 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -33,10 +33,6 @@ test: insert-conflict-do-update
test: insert-conflict-do-update-2
test: insert-conflict-do-update-3
test: insert-conflict-toast
-test: merge-insert-update
-test: merge-delete
-test: merge-update
-test: merge-match-recheck
test: delete-abort-savept
test: delete-abort-savept-2
test: aborted-keyrevoke
diff --git a/src/test/isolation/specs/merge-delete.spec b/src/test/isolation/specs/merge-delete.spec
deleted file mode 100644
index 656954f8474..00000000000
--- a/src/test/isolation/specs/merge-delete.spec
+++ /dev/null
@@ -1,51 +0,0 @@
-# MERGE DELETE
-#
-# This test looks at the interactions involving concurrent deletes
-# comparing the behavior of MERGE, DELETE and UPDATE
-
-setup
-{
- CREATE TABLE target (key int primary key, val text);
- INSERT INTO target VALUES (1, 'setup1');
-}
-
-teardown
-{
- DROP TABLE target;
-}
-
-session "s1"
-setup
-{
- BEGIN ISOLATION LEVEL READ COMMITTED;
-}
-step "delete" { DELETE FROM target t WHERE t.key = 1; }
-step "merge_delete" { MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE; }
-step "c1" { COMMIT; }
-step "a1" { ABORT; }
-
-session "s2"
-setup
-{
- BEGIN ISOLATION LEVEL READ COMMITTED;
-}
-step "update1" { UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; }
-step "merge2" { MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
-step "select2" { SELECT * FROM target; }
-step "c2" { COMMIT; }
-
-# Basic effects
-permutation "delete" "c1" "select2" "c2"
-permutation "merge_delete" "c1" "select2" "c2"
-
-# One after the other, no concurrency
-permutation "delete" "c1" "update1" "select2" "c2"
-permutation "merge_delete" "c1" "update1" "select2" "c2"
-permutation "delete" "c1" "merge2" "select2" "c2"
-permutation "merge_delete" "c1" "merge2" "select2" "c2"
-
-# Now with concurrency
-permutation "delete" "update1" "c1" "select2" "c2"
-permutation "merge_delete" "update1" "c1" "select2" "c2"
-permutation "delete" "merge2" "c1" "select2" "c2"
-permutation "merge_delete" "merge2" "c1" "select2" "c2"
diff --git a/src/test/isolation/specs/merge-insert-update.spec b/src/test/isolation/specs/merge-insert-update.spec
deleted file mode 100644
index 704492be1f3..00000000000
--- a/src/test/isolation/specs/merge-insert-update.spec
+++ /dev/null
@@ -1,52 +0,0 @@
-# MERGE INSERT UPDATE
-#
-# This looks at how we handle concurrent INSERTs, illustrating how the
-# behavior differs from INSERT ... ON CONFLICT
-
-setup
-{
- CREATE TABLE target (key int primary key, val text);
-}
-
-teardown
-{
- DROP TABLE target;
-}
-
-session "s1"
-setup
-{
- BEGIN ISOLATION LEVEL READ COMMITTED;
-}
-step "merge1" { MERGE INTO target t USING (SELECT 1 as key, 'merge1' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge1'; }
-step "delete1" { DELETE FROM target WHERE key = 1; }
-step "insert1" { INSERT INTO target VALUES (1, 'insert1'); }
-step "c1" { COMMIT; }
-step "a1" { ABORT; }
-
-session "s2"
-setup
-{
- BEGIN ISOLATION LEVEL READ COMMITTED;
-}
-step "merge2" { MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge2'; }
-
-step "merge2i" { MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN MATCHED THEN UPDATE set val = t.val || ' updated by merge2'; }
-
-step "select2" { SELECT * FROM target; }
-step "c2" { COMMIT; }
-step "a2" { ABORT; }
-
-# Basic effects
-permutation "merge1" "c1" "select2" "c2"
-permutation "merge1" "c1" "merge2" "select2" "c2"
-
-# check concurrent inserts
-permutation "insert1" "merge2" "c1" "select2" "c2"
-permutation "merge1" "merge2" "c1" "select2" "c2"
-permutation "merge1" "merge2" "a1" "select2" "c2"
-
-# check how we handle when visible row has been concurrently deleted, then same key re-inserted
-permutation "delete1" "insert1" "c1" "merge2" "select2" "c2"
-permutation "delete1" "insert1" "merge2" "c1" "select2" "c2"
-permutation "delete1" "insert1" "merge2i" "c1" "select2" "c2"
diff --git a/src/test/isolation/specs/merge-match-recheck.spec b/src/test/isolation/specs/merge-match-recheck.spec
deleted file mode 100644
index 193033da172..00000000000
--- a/src/test/isolation/specs/merge-match-recheck.spec
+++ /dev/null
@@ -1,79 +0,0 @@
-# MERGE MATCHED RECHECK
-#
-# This test looks at what happens when we have complex
-# WHEN MATCHED AND conditions and a concurrent UPDATE causes a
-# recheck of the AND condition on the new row
-
-setup
-{
- CREATE TABLE target (key int primary key, balance integer, status text, val text);
- INSERT INTO target VALUES (1, 160, 's1', 'setup');
-}
-
-teardown
-{
- DROP TABLE target;
-}
-
-session "s1"
-setup
-{
- BEGIN ISOLATION LEVEL READ COMMITTED;
-}
-step "merge_status"
-{
- MERGE INTO target t
- USING (SELECT 1 as key) s
- ON s.key = t.key
- WHEN MATCHED AND status = 's1' THEN
- UPDATE SET status = 's2', val = t.val || ' when1'
- WHEN MATCHED AND status = 's2' THEN
- UPDATE SET status = 's3', val = t.val || ' when2'
- WHEN MATCHED AND status = 's3' THEN
- UPDATE SET status = 's4', val = t.val || ' when3';
-}
-
-step "merge_bal"
-{
- MERGE INTO target t
- USING (SELECT 1 as key) s
- ON s.key = t.key
- WHEN MATCHED AND balance < 100 THEN
- UPDATE SET balance = balance * 2, val = t.val || ' when1'
- WHEN MATCHED AND balance < 200 THEN
- UPDATE SET balance = balance * 4, val = t.val || ' when2'
- WHEN MATCHED AND balance < 300 THEN
- UPDATE SET balance = balance * 8, val = t.val || ' when3';
-}
-
-step "select1" { SELECT * FROM target; }
-step "c1" { COMMIT; }
-step "a1" { ABORT; }
-
-session "s2"
-setup
-{
- BEGIN ISOLATION LEVEL READ COMMITTED;
-}
-step "update1" { UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1; }
-step "update2" { UPDATE target t SET status = 's2', val = t.val || ' updated by update2' WHERE t.key = 1; }
-step "update3" { UPDATE target t SET status = 's3', val = t.val || ' updated by update3' WHERE t.key = 1; }
-step "update5" { UPDATE target t SET status = 's5', val = t.val || ' updated by update5' WHERE t.key = 1; }
-step "update_bal1" { UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1; }
-step "select2" { SELECT * FROM target; }
-step "c2" { COMMIT; }
-
-# merge_status sees concurrently updated row and rechecks WHEN conditions, but recheck passes and final status = 's2'
-permutation "update1" "merge_status" "c2" "select1" "c1"
-
-# merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final status = 's3' not 's2'
-permutation "update2" "merge_status" "c2" "select1" "c1"
-
-# merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final status = 's4' not 's2'
-permutation "update3" "merge_status" "c2" "select1" "c1"
-
-# merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, but we skip update and MERGE does nothing
-permutation "update5" "merge_status" "c2" "select1" "c1"
-
-# merge_bal sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final balance = 100 not 640
-permutation "update_bal1" "merge_bal" "c2" "select1" "c1"
diff --git a/src/test/isolation/specs/merge-update.spec b/src/test/isolation/specs/merge-update.spec
deleted file mode 100644
index 625b477eb9f..00000000000
--- a/src/test/isolation/specs/merge-update.spec
+++ /dev/null
@@ -1,133 +0,0 @@
-# MERGE UPDATE
-#
-# This test exercises atypical cases
-# 1. UPDATEs of PKs that change the join in the ON clause
-# 2. UPDATEs with WHEN AND conditions that would fail after concurrent update
-# 3. UPDATEs with extra ON conditions that would fail after concurrent update
-
-setup
-{
- CREATE TABLE target (key int primary key, val text);
- INSERT INTO target VALUES (1, 'setup1');
-
- CREATE TABLE pa_target (key integer, val text)
- PARTITION BY LIST (key);
- CREATE TABLE part1 (key integer, val text);
- CREATE TABLE part2 (val text, key integer);
- CREATE TABLE part3 (key integer, val text);
-
- ALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);
- ALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);
- ALTER TABLE pa_target ATTACH PARTITION part3 DEFAULT;
-
- INSERT INTO pa_target VALUES (1, 'initial');
- INSERT INTO pa_target VALUES (2, 'initial');
-}
-
-teardown
-{
- DROP TABLE target;
- DROP TABLE pa_target CASCADE;
-}
-
-session "s1"
-setup
-{
- BEGIN ISOLATION LEVEL READ COMMITTED;
-}
-step "merge1"
-{
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-}
-step "pa_merge1"
-{
- MERGE INTO pa_target t
- USING (SELECT 1 as key, 'pa_merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set val = t.val || ' updated by ' || s.val;
-}
-step "pa_merge2"
-{
- MERGE INTO pa_target t
- USING (SELECT 1 as key, 'pa_merge1' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-}
-step "c1" { COMMIT; }
-step "a1" { ABORT; }
-
-session "s2"
-setup
-{
- BEGIN ISOLATION LEVEL READ COMMITTED;
-}
-step "merge2a"
-{
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge2a' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-}
-step "merge2b"
-{
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge2b' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED AND t.key < 2 THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-}
-step "merge2c"
-{
- MERGE INTO target t
- USING (SELECT 1 as key, 'merge2c' as val) s
- ON s.key = t.key AND t.key < 2
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-}
-step "pa_merge2a"
-{
- MERGE INTO pa_target t
- USING (SELECT 1 as key, 'pa_merge2a' as val) s
- ON s.key = t.key
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.key, s.val)
- WHEN MATCHED THEN
- UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
-}
-step "select2" { SELECT * FROM target; }
-step "pa_select2" { SELECT * FROM pa_target; }
-step "c2" { COMMIT; }
-
-# Basic effects
-permutation "merge1" "c1" "select2" "c2"
-
-# One after the other, no concurrency
-permutation "merge1" "c1" "merge2a" "select2" "c2"
-
-# Now with concurrency
-permutation "merge1" "merge2a" "c1" "select2" "c2"
-permutation "merge1" "merge2a" "a1" "select2" "c2"
-permutation "merge1" "merge2b" "c1" "select2" "c2"
-permutation "merge1" "merge2c" "c1" "select2" "c2"
-permutation "pa_merge1" "pa_merge2a" "c1" "pa_select2" "c2"
-permutation "pa_merge2" "pa_merge2a" "c1" "pa_select2" "c2" # fails
-permutation "pa_merge2" "c1" "pa_merge2a" "pa_select2" "c2" # succeeds
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 3a6016c80a6..d7d5178f5d8 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -386,58 +386,3 @@ CREATE TABLE itest_child PARTITION OF itest_parent (
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
ERROR: identity columns are not supported on partitions
DROP TABLE itest_parent;
--- MERGE tests
-CREATE TABLE itest14 (a int GENERATED ALWAYS AS IDENTITY, b text);
-CREATE TABLE itest15 (a int GENERATED BY DEFAULT AS IDENTITY, b text);
-MERGE INTO itest14 t
-USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) VALUES (s.s_a, s.s_b);
-ERROR: cannot insert into column "a"
-DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
-HINT: Use OVERRIDING SYSTEM VALUE to override.
-MERGE INTO itest14 t
-USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
-ERROR: cannot insert into column "a"
-DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
-HINT: Use OVERRIDING SYSTEM VALUE to override.
-MERGE INTO itest14 t
-USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
-MERGE INTO itest15 t
-USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) VALUES (s.s_a, s.s_b);
-MERGE INTO itest15 t
-USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
-MERGE INTO itest15 t
-USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
-SELECT * FROM itest14;
- a | b
-----+-------------------
- 30 | inserted by merge
-(1 row)
-
-SELECT * FROM itest15;
- a | b
-----+-------------------
- 10 | inserted by merge
- 1 | inserted by merge
- 30 | inserted by merge
-(3 rows)
-
-DROP TABLE itest14;
-DROP TABLE itest15;
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
deleted file mode 100644
index 03e30ef5599..00000000000
--- a/src/test/regress/expected/merge.out
+++ /dev/null
@@ -1,1672 +0,0 @@
---
--- MERGE
---
---\set VERBOSITY verbose
---set debug_print_rewritten = true;
---set debug_print_parse = true;
---set debug_print_pretty = true;
-CREATE USER merge_privs;
-CREATE USER merge_no_privs;
-DROP TABLE IF EXISTS target;
-NOTICE: table "target" does not exist, skipping
-DROP TABLE IF EXISTS source;
-NOTICE: table "source" does not exist, skipping
-CREATE TABLE target (tid integer, balance integer);
-CREATE TABLE source (sid integer, delta integer); --no index
-INSERT INTO target VALUES (1, 10);
-INSERT INTO target VALUES (2, 20);
-INSERT INTO target VALUES (3, 30);
-SELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;
- matched | tid | balance | sid | delta
----------+-----+---------+-----+-------
- t | 1 | 10 | |
- t | 2 | 20 | |
- t | 3 | 30 | |
-(3 rows)
-
-ALTER TABLE target OWNER TO merge_privs;
-ALTER TABLE source OWNER TO merge_privs;
-CREATE TABLE target2 (tid integer, balance integer);
-CREATE TABLE source2 (sid integer, delta integer);
-ALTER TABLE target2 OWNER TO merge_no_privs;
-ALTER TABLE source2 OWNER TO merge_no_privs;
-GRANT INSERT ON target TO merge_no_privs;
-SET SESSION AUTHORIZATION merge_privs;
-EXPLAIN (COSTS OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
- QUERY PLAN
-------------------------------------------
- Merge on target t
- -> Merge Join
- Merge Cond: (t_1.tid = s.sid)
- -> Sort
- Sort Key: t_1.tid
- -> Seq Scan on target t_1
- -> Sort
- Sort Key: s.sid
- -> Seq Scan on source s
-(9 rows)
-
---
--- Errors
---
-MERGE INTO target t RANDOMWORD
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-ERROR: syntax error at or near "RANDOMWORD"
-LINE 1: MERGE INTO target t RANDOMWORD
- ^
--- MATCHED/INSERT error
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- INSERT DEFAULT VALUES
-;
-ERROR: syntax error at or near "INSERT"
-LINE 5: INSERT DEFAULT VALUES
- ^
--- incorrectly specifying INTO target
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT INTO target DEFAULT VALUES
-;
-ERROR: syntax error at or near "INTO"
-LINE 5: INSERT INTO target DEFAULT VALUES
- ^
--- Multiple VALUES clause
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (1,1), (2,2);
-ERROR: syntax error at or near ","
-LINE 5: INSERT VALUES (1,1), (2,2);
- ^
-;
--- SELECT query for INSERT
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT SELECT (1, 1);
-ERROR: syntax error at or near "SELECT"
-LINE 5: INSERT SELECT (1, 1);
- ^
-;
--- NOT MATCHED/UPDATE
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- UPDATE SET balance = 0
-;
-ERROR: syntax error at or near "UPDATE"
-LINE 5: UPDATE SET balance = 0
- ^
--- UPDATE tablename
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE target SET balance = 0
-;
-ERROR: syntax error at or near "target"
-LINE 5: UPDATE target SET balance = 0
- ^
--- unsupported relation types
--- view
-CREATE VIEW tv AS SELECT * FROM target;
-MERGE INTO tv t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-ERROR: MERGE is not supported for this relation type
-DROP VIEW tv;
--- materialized view
-CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
-MERGE INTO mv t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-ERROR: MERGE is not supported for this relation type
-DROP MATERIALIZED VIEW mv;
--- inherited table
-CREATE TABLE inhp (tid int, balance int);
-CREATE TABLE child1() INHERITS (inhp);
-CREATE TABLE child2() INHERITS (child1);
-MERGE INTO inhp t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-ERROR: MERGE is not supported for relations with inheritance
-MERGE INTO child1 t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-ERROR: MERGE is not supported for relations with inheritance
--- this should be ok
-MERGE INTO child2 t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-DROP TABLE inhp, child1, child2;
--- permissions
-MERGE INTO target
-USING source2
-ON target.tid = source2.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-ERROR: permission denied for table source2
-GRANT INSERT ON target TO merge_no_privs;
-SET SESSION AUTHORIZATION merge_no_privs;
-MERGE INTO target
-USING source2
-ON target.tid = source2.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-ERROR: permission denied for table target
-GRANT UPDATE ON target2 TO merge_privs;
-SET SESSION AUTHORIZATION merge_privs;
-MERGE INTO target2
-USING source
-ON target2.tid = source.sid
-WHEN MATCHED THEN
- DELETE
-;
-ERROR: permission denied for table target2
-MERGE INTO target2
-USING source
-ON target2.tid = source.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES
-;
-ERROR: permission denied for table target2
--- check if the target can be accessed from source relation subquery; we should
--- not be able to do so
-MERGE INTO target t
-USING (SELECT * FROM source WHERE t.tid > sid) s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES
-;
-ERROR: invalid reference to FROM-clause entry for table "t"
-LINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s
- ^
-HINT: There is an entry for table "t", but it cannot be referenced from this part of the query.
---
--- initial tests
---
--- zero rows in source has no effect
-MERGE INTO target
-USING source
-ON target.tid = source.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES
-;
-ROLLBACK;
--- insert some non-matching source rows to work from
-INSERT INTO source VALUES (4, 40);
-SELECT * FROM source ORDER BY sid;
- sid | delta
------+-------
- 4 | 40
-(1 row)
-
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
-(3 rows)
-
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- DO NOTHING
-;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
- |
-(4 rows)
-
-ROLLBACK;
--- index plans
-INSERT INTO target SELECT generate_series(1000,2500), 0;
-ALTER TABLE target ADD PRIMARY KEY (tid);
-ANALYZE target;
-EXPLAIN (COSTS OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
- QUERY PLAN
-------------------------------------------
- Merge on target t
- -> Hash Join
- Hash Cond: (s.sid = t_1.tid)
- -> Seq Scan on source s
- -> Hash
- -> Seq Scan on target t_1
-(6 rows)
-
-EXPLAIN (COSTS OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
- QUERY PLAN
-------------------------------------------
- Merge on target t
- -> Hash Join
- Hash Cond: (s.sid = t_1.tid)
- -> Seq Scan on source s
- -> Hash
- -> Seq Scan on target t_1
-(6 rows)
-
-EXPLAIN (COSTS OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (4, NULL);
- QUERY PLAN
-------------------------------------------
- Merge on target t
- -> Hash Left Join
- Hash Cond: (s.sid = t_1.tid)
- -> Seq Scan on source s
- -> Hash
- -> Seq Scan on target t_1
-(6 rows)
-
-;
-DELETE FROM target WHERE tid > 100;
-ANALYZE target;
--- insert some matching source rows to work from
-INSERT INTO source VALUES (2, 5);
-INSERT INTO source VALUES (3, 20);
-SELECT * FROM source ORDER BY sid;
- sid | delta
------+-------
- 2 | 5
- 3 | 20
- 4 | 40
-(3 rows)
-
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
-(3 rows)
-
--- equivalent of an UPDATE join
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 0
- 3 | 0
-(3 rows)
-
-ROLLBACK;
--- equivalent of a DELETE join
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
-(1 row)
-
-ROLLBACK;
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (4, NULL)
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
- 4 |
-(4 rows)
-
-ROLLBACK;
--- duplicate source row causes multiple target row update ERROR
-INSERT INTO source VALUES (2, 5);
-SELECT * FROM source ORDER BY sid;
- sid | delta
------+-------
- 2 | 5
- 2 | 5
- 3 | 20
- 4 | 40
-(4 rows)
-
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
-(3 rows)
-
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-ERROR: MERGE command cannot affect row a second time
-HINT: Ensure that not more than one source row matches any one target row
-ROLLBACK;
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-ERROR: MERGE command cannot affect row a second time
-HINT: Ensure that not more than one source row matches any one target row
-ROLLBACK;
--- correct source data
-DELETE FROM source WHERE sid = 2;
-INSERT INTO source VALUES (2, 5);
-SELECT * FROM source ORDER BY sid;
- sid | delta
------+-------
- 2 | 5
- 3 | 20
- 4 | 40
-(3 rows)
-
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
-(3 rows)
-
--- remove constraints
-alter table target drop CONSTRAINT target_pkey;
-alter table target alter column tid drop not null;
--- multiple actions
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (4, 4)
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 0
- 3 | 0
- 4 | 4
-(4 rows)
-
-ROLLBACK;
--- should be equivalent
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-WHEN NOT MATCHED THEN
- INSERT VALUES (4, 4);
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 0
- 3 | 0
- 4 | 4
-(4 rows)
-
-ROLLBACK;
--- column references
--- do a simple equivalent of an UPDATE join
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = t.balance + s.delta
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 25
- 3 | 50
-(3 rows)
-
-ROLLBACK;
--- do a simple equivalent of an INSERT SELECT
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
- 4 | 40
-(4 rows)
-
-ROLLBACK;
--- and again with explicitly identified column list
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
- 4 | 40
-(4 rows)
-
-ROLLBACK;
--- and again with a subtle error: referring to non-existent target row for NOT MATCHED
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (t.tid, s.delta)
-;
-ERROR: invalid reference to FROM-clause entry for table "t"
-LINE 5: INSERT (tid, balance) VALUES (t.tid, s.delta)
- ^
-HINT: There is an entry for table "t", but it cannot be referenced from this part of the query.
--- and again with a constant ON clause
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON (SELECT true)
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (t.tid, s.delta)
-;
-ERROR: invalid reference to FROM-clause entry for table "t"
-LINE 5: INSERT (tid, balance) VALUES (t.tid, s.delta)
- ^
-HINT: There is an entry for table "t", but it cannot be referenced from this part of the query.
-SELECT * FROM target ORDER BY tid;
-ERROR: current transaction is aborted, commands ignored until end of transaction block
-ROLLBACK;
--- now the classic UPSERT
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = t.balance + s.delta
-WHEN NOT MATCHED THEN
- INSERT VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 25
- 3 | 50
- 4 | 40
-(4 rows)
-
-ROLLBACK;
--- unreachable WHEN clause should ERROR
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN /* Terminal WHEN clause for MATCHED */
- DELETE
-WHEN MATCHED AND s.delta > 0 THEN
- UPDATE SET balance = t.balance - s.delta
-;
-ERROR: unreachable WHEN clause specified after unconditional WHEN clause
-ROLLBACK;
--- conditional WHEN clause
-CREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1);
-CREATE TABLE wq_source (balance integer, sid integer);
-INSERT INTO wq_source (sid, balance) VALUES (1, 100);
-BEGIN;
--- try a simple INSERT with default values first
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | -1
-(1 row)
-
-ROLLBACK;
--- this time with a FALSE condition
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND FALSE THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
- tid | balance
------+---------
-(0 rows)
-
--- this time with an actual condition which returns false
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND s.balance <> 100 THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
- tid | balance
------+---------
-(0 rows)
-
-BEGIN;
--- and now with a condition which returns true
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND s.balance = 100 THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | -1
-(1 row)
-
-ROLLBACK;
--- conditions in the NOT MATCHED clause can only refer to source columns
-BEGIN;
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND t.balance = 100 THEN
- INSERT (tid) VALUES (s.sid);
-ERROR: invalid reference to FROM-clause entry for table "t"
-LINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN
- ^
-HINT: There is an entry for table "t", but it cannot be referenced from this part of the query.
-SELECT * FROM wq_target;
-ERROR: current transaction is aborted, commands ignored until end of transaction block
-ROLLBACK;
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND s.balance = 100 THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | -1
-(1 row)
-
--- conditions in MATCHED clause can refer to both source and target
-SELECT * FROM wq_source;
- balance | sid
----------+-----
- 100 | 1
-(1 row)
-
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND s.balance = 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | 99
-(1 row)
-
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | 99
-(1 row)
-
--- check if AND works
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | 99
-(1 row)
-
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | 199
-(1 row)
-
--- check if OR works
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | 199
-(1 row)
-
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | 299
-(1 row)
-
--- check if subqueries work in the conditions?
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN
- UPDATE SET balance = t.balance + s.balance;
--- check if we can access system columns in the conditions
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.xmin = t.xmax THEN
- UPDATE SET balance = t.balance + s.balance;
-ERROR: system column "xmin" reference in WHEN AND condition is invalid
-LINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN
- ^
-ALTER TABLE wq_target SET WITH OIDS;
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | 399
-(1 row)
-
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.oid >= 0 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
- tid | balance
------+---------
- 1 | 499
-(1 row)
-
--- test preventing WHEN AND conditions from writing to the database
-create or replace function merge_when_and_write() returns boolean
-language plpgsql as
-$$
-BEGIN
- INSERT INTO target VALUES (100, 100);
- RETURN TRUE;
-END;
-$$;
-BEGIN;
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND (merge_when_and_write()) THEN
- UPDATE SET balance = t.balance + s.balance;
-ROLLBACK;
-drop function merge_when_and_write();
-DROP TABLE wq_target, wq_source;
--- test triggers
-create or replace function merge_trigfunc () returns trigger
-language plpgsql as
-$$
-BEGIN
- RAISE NOTICE '% % % trigger', TG_WHEN, TG_OP, TG_LEVEL;
- IF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN
- IF (TG_OP = 'DELETE') THEN
- RETURN OLD;
- ELSE
- RETURN NEW;
- END IF;
- ELSE
- RETURN NULL;
- END IF;
-END;
-$$;
-CREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
--- now the classic UPSERT, with a DELETE
-BEGIN;
-UPDATE target SET balance = 0 WHERE tid = 3;
-NOTICE: BEFORE UPDATE STATEMENT trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER UPDATE STATEMENT trigger
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED AND t.balance > s.delta THEN
- UPDATE SET balance = t.balance - s.delta
-WHEN MATCHED THEN
- DELETE
-WHEN NOT MATCHED THEN
- INSERT VALUES (s.sid, s.delta)
-;
-NOTICE: BEFORE INSERT STATEMENT trigger
-NOTICE: BEFORE UPDATE STATEMENT trigger
-NOTICE: BEFORE DELETE STATEMENT trigger
-NOTICE: BEFORE INSERT ROW trigger
-NOTICE: BEFORE DELETE ROW trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: AFTER INSERT ROW trigger
-NOTICE: AFTER DELETE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER DELETE STATEMENT trigger
-NOTICE: AFTER UPDATE STATEMENT trigger
-NOTICE: AFTER INSERT STATEMENT trigger
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 15
- 4 | 40
-(3 rows)
-
-ROLLBACK;
--- test from PL/pgSQL
--- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO
-BEGIN;
-DO LANGUAGE plpgsql $$
-BEGIN
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED AND t.balance > s.delta THEN
- UPDATE SET balance = t.balance - s.delta
-;
-END;
-$$;
-NOTICE: BEFORE UPDATE STATEMENT trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER UPDATE STATEMENT trigger
-ROLLBACK;
---source constants
-BEGIN;
-MERGE INTO target t
-USING (SELECT 9 AS sid, 57 AS delta) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-NOTICE: BEFORE INSERT STATEMENT trigger
-NOTICE: BEFORE INSERT ROW trigger
-NOTICE: AFTER INSERT ROW trigger
-NOTICE: AFTER INSERT STATEMENT trigger
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
- 9 | 57
-(4 rows)
-
-ROLLBACK;
---source query
-BEGIN;
-MERGE INTO target t
-USING (SELECT sid, delta FROM source WHERE delta > 0) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-NOTICE: BEFORE INSERT STATEMENT trigger
-NOTICE: BEFORE INSERT ROW trigger
-NOTICE: AFTER INSERT ROW trigger
-NOTICE: AFTER INSERT STATEMENT trigger
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
- 4 | 40
-(4 rows)
-
-ROLLBACK;
-BEGIN;
-MERGE INTO target t
-USING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.newname)
-;
-NOTICE: BEFORE INSERT STATEMENT trigger
-NOTICE: BEFORE INSERT ROW trigger
-NOTICE: AFTER INSERT ROW trigger
-NOTICE: AFTER INSERT STATEMENT trigger
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
- 4 | 40
-(4 rows)
-
-ROLLBACK;
---self-merge
-BEGIN;
-MERGE INTO target t1
-USING target t2
-ON t1.tid = t2.tid
-WHEN MATCHED THEN
- UPDATE SET balance = t1.balance + t2.balance
-WHEN NOT MATCHED THEN
- INSERT VALUES (t2.tid, t2.balance)
-;
-NOTICE: BEFORE INSERT STATEMENT trigger
-NOTICE: BEFORE UPDATE STATEMENT trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER UPDATE STATEMENT trigger
-NOTICE: AFTER INSERT STATEMENT trigger
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 20
- 2 | 40
- 3 | 60
-(3 rows)
-
-ROLLBACK;
-BEGIN;
-MERGE INTO target t
-USING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-NOTICE: BEFORE INSERT STATEMENT trigger
-NOTICE: AFTER INSERT STATEMENT trigger
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
-(3 rows)
-
-ROLLBACK;
-BEGIN;
-MERGE INTO target t
-USING
-(SELECT sid, max(delta) AS delta
- FROM source
- GROUP BY sid
- HAVING count(*) = 1
- ORDER BY sid ASC) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-NOTICE: BEFORE INSERT STATEMENT trigger
-NOTICE: BEFORE INSERT ROW trigger
-NOTICE: AFTER INSERT ROW trigger
-NOTICE: AFTER INSERT STATEMENT trigger
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 30
- 4 | 40
-(4 rows)
-
-ROLLBACK;
--- plpgsql parameters and results
-BEGIN;
-CREATE FUNCTION merge_func (p_id integer, p_bal integer)
-RETURNS INTEGER
-LANGUAGE plpgsql
-AS $$
-DECLARE
- result integer;
-BEGIN
-MERGE INTO target t
-USING (SELECT p_id AS sid) AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = t.balance - p_bal
-;
-IF FOUND THEN
- GET DIAGNOSTICS result := ROW_COUNT;
-END IF;
-RETURN result;
-END;
-$$;
-SELECT merge_func(3, 4);
-NOTICE: BEFORE UPDATE STATEMENT trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER UPDATE STATEMENT trigger
- merge_func
-------------
- 1
-(1 row)
-
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 10
- 2 | 20
- 3 | 26
-(3 rows)
-
-ROLLBACK;
--- PREPARE
-BEGIN;
-prepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;
-execute foom;
-NOTICE: BEFORE UPDATE STATEMENT trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER UPDATE STATEMENT trigger
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 1
- 2 | 20
- 3 | 30
-(3 rows)
-
-ROLLBACK;
-BEGIN;
-PREPARE foom2 (integer, integer) AS
-MERGE INTO target t
-USING (SELECT 1) s
-ON t.tid = $1
-WHEN MATCHED THEN
-UPDATE SET balance = $2;
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-execute foom2 (1, 1);
-NOTICE: BEFORE UPDATE STATEMENT trigger
-NOTICE: BEFORE UPDATE ROW trigger
-NOTICE: AFTER UPDATE ROW trigger
-NOTICE: AFTER UPDATE STATEMENT trigger
-SELECT * FROM target ORDER BY tid;
- tid | balance
------+---------
- 1 | 1
- 2 | 20
- 3 | 30
-(3 rows)
-
-ROLLBACK;
--- subqueries in source relation
-CREATE TABLE sq_target (tid integer NOT NULL, balance integer);
-CREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0);
-INSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);
-INSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);
-BEGIN;
-MERGE INTO sq_target t
-USING (SELECT * FROM sq_source) s
-ON tid = sid
-WHEN MATCHED AND t.balance > delta THEN
- UPDATE SET balance = t.balance + delta;
-SELECT * FROM sq_target;
- tid | balance
------+---------
- 3 | 300
- 1 | 110
- 2 | 220
-(3 rows)
-
-ROLLBACK;
--- try a view
-CREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;
-BEGIN;
-MERGE INTO sq_target
-USING v
-ON tid = sid
-WHEN MATCHED THEN
- UPDATE SET balance = v.balance + delta;
-SELECT * FROM sq_target;
- tid | balance
------+---------
- 2 | 200
- 3 | 300
- 1 | 10
-(3 rows)
-
-ROLLBACK;
--- ambiguous reference to a column
-BEGIN;
-MERGE INTO sq_target
-USING v
-ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
- UPDATE SET balance = balance + delta
-WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
-WHEN MATCHED AND tid < 2 THEN
- DELETE;
-ERROR: column reference "balance" is ambiguous
-LINE 5: UPDATE SET balance = balance + delta
- ^
-ROLLBACK;
-BEGIN;
-INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
- UPDATE SET balance = t.balance + delta
-WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
-WHEN MATCHED AND tid < 2 THEN
- DELETE;
-SELECT * FROM sq_target;
- tid | balance
------+---------
- 2 | 200
- 3 | 300
- -1 | -11
-(3 rows)
-
-ROLLBACK;
--- CTEs
-BEGIN;
-INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
-WITH targq AS (
- SELECT * FROM v
-)
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
- UPDATE SET balance = t.balance + delta
-WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
-WHEN MATCHED AND tid < 2 THEN
- DELETE
-;
-ROLLBACK;
--- RETURNING
-BEGIN;
-INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
- UPDATE SET balance = t.balance + delta
-WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
-WHEN MATCHED AND tid < 2 THEN
- DELETE
-RETURNING *
-;
-ERROR: syntax error at or near "RETURNING"
-LINE 10: RETURNING *
- ^
-ROLLBACK;
--- EXPLAIN
-CREATE TABLE ex_mtarget (a int, b int);
-CREATE TABLE ex_msource (a int, b int);
-INSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;
-INSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;
--- only updates
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = t.b + 1;
--- only updates to selected tuples
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN MATCHED AND t.a < 10 THEN
- UPDATE SET b = t.b + 1;
--- updates + deletes
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN MATCHED AND t.a < 10 THEN
- UPDATE SET b = t.b + 1
-WHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN
- DELETE;
--- only inserts
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN NOT MATCHED AND s.a < 10 THEN
- INSERT VALUES (a, b);
--- all three
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN MATCHED AND t.a < 10 THEN
- UPDATE SET b = t.b + 1
-WHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN
- DELETE
-WHEN NOT MATCHED AND s.a < 20 THEN
- INSERT VALUES (a, b);
-DROP TABLE ex_msource, ex_mtarget;
--- Subqueries
-BEGIN;
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED THEN
- UPDATE SET balance = (SELECT count(*) FROM sq_target)
-;
-SELECT * FROM sq_target WHERE tid = 1;
- tid | balance
------+---------
- 1 | 3
-(1 row)
-
-ROLLBACK;
-BEGIN;
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN
- UPDATE SET balance = 42
-;
-SELECT * FROM sq_target WHERE tid = 1;
- tid | balance
------+---------
- 1 | 42
-(1 row)
-
-ROLLBACK;
-BEGIN;
-MERGE INTO sq_target t
-USING v
-ON tid = sid AND (SELECT count(*) > 0 FROM sq_target)
-WHEN MATCHED THEN
- UPDATE SET balance = 42
-;
-SELECT * FROM sq_target WHERE tid = 1;
- tid | balance
------+---------
- 1 | 42
-(1 row)
-
-ROLLBACK;
-DROP TABLE sq_target, sq_source CASCADE;
-NOTICE: drop cascades to view v
-CREATE TABLE pa_target (tid integer, balance float, val text)
- PARTITION BY LIST (tid);
-CREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4);
-CREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6);
-CREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9);
-CREATE TABLE part4 PARTITION OF pa_target DEFAULT;
-CREATE TABLE pa_source (sid integer, delta float);
--- insert many rows to the source table
-INSERT INTO pa_source SELECT id, id * 10 FROM generate_series(1,14) AS id;
--- insert a few rows in the target table (odd numbered tid)
-INSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;
--- try simple MERGE
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
- tid | balance | val
------+---------+--------------------------
- 1 | 110 | initial updated by merge
- 2 | 20 | inserted by merge
- 3 | 330 | initial updated by merge
- 4 | 40 | inserted by merge
- 5 | 550 | initial updated by merge
- 6 | 60 | inserted by merge
- 7 | 770 | initial updated by merge
- 8 | 80 | inserted by merge
- 9 | 990 | initial updated by merge
- 10 | 100 | inserted by merge
- 11 | 1210 | initial updated by merge
- 12 | 120 | inserted by merge
- 13 | 1430 | initial updated by merge
- 14 | 140 | inserted by merge
-(14 rows)
-
-ROLLBACK;
--- same with a constant qual
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid AND tid = 1
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
- tid | balance | val
------+---------+--------------------------
- 1 | 110 | initial updated by merge
- 2 | 20 | inserted by merge
- 3 | 30 | inserted by merge
- 3 | 300 | initial
- 4 | 40 | inserted by merge
- 5 | 500 | initial
- 5 | 50 | inserted by merge
- 6 | 60 | inserted by merge
- 7 | 700 | initial
- 7 | 70 | inserted by merge
- 8 | 80 | inserted by merge
- 9 | 90 | inserted by merge
- 9 | 900 | initial
- 10 | 100 | inserted by merge
- 11 | 1100 | initial
- 11 | 110 | inserted by merge
- 12 | 120 | inserted by merge
- 13 | 1300 | initial
- 13 | 130 | inserted by merge
- 14 | 140 | inserted by merge
-(20 rows)
-
-ROLLBACK;
--- try updating the partition key column
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
- tid | balance | val
------+---------+--------------------------
- 2 | 110 | initial updated by merge
- 2 | 20 | inserted by merge
- 4 | 40 | inserted by merge
- 4 | 330 | initial updated by merge
- 6 | 550 | initial updated by merge
- 6 | 60 | inserted by merge
- 8 | 80 | inserted by merge
- 8 | 770 | initial updated by merge
- 10 | 990 | initial updated by merge
- 10 | 100 | inserted by merge
- 12 | 1210 | initial updated by merge
- 12 | 120 | inserted by merge
- 14 | 1430 | initial updated by merge
- 14 | 140 | inserted by merge
-(14 rows)
-
-ROLLBACK;
-DROP TABLE pa_target CASCADE;
--- The target table is partitioned in the same way, but this time by attaching
--- partitions which have columns in different order, dropped columns etc.
-CREATE TABLE pa_target (tid integer, balance float, val text)
- PARTITION BY LIST (tid);
-CREATE TABLE part1 (tid integer, balance float, val text);
-CREATE TABLE part2 (balance float, tid integer, val text);
-CREATE TABLE part3 (tid integer, balance float, val text);
-CREATE TABLE part4 (extraid text, tid integer, balance float, val text);
-ALTER TABLE part4 DROP COLUMN extraid;
-ALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);
-ALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);
-ALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);
-ALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;
--- insert a few rows in the target table (odd numbered tid)
-INSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;
--- try simple MERGE
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
- tid | balance | val
------+---------+--------------------------
- 1 | 110 | initial updated by merge
- 2 | 20 | inserted by merge
- 3 | 330 | initial updated by merge
- 4 | 40 | inserted by merge
- 5 | 550 | initial updated by merge
- 6 | 60 | inserted by merge
- 7 | 770 | initial updated by merge
- 8 | 80 | inserted by merge
- 9 | 990 | initial updated by merge
- 10 | 100 | inserted by merge
- 11 | 1210 | initial updated by merge
- 12 | 120 | inserted by merge
- 13 | 1430 | initial updated by merge
- 14 | 140 | inserted by merge
-(14 rows)
-
-ROLLBACK;
--- same with a constant qual
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid AND tid = 1
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
- tid | balance | val
------+---------+--------------------------
- 1 | 110 | initial updated by merge
- 2 | 20 | inserted by merge
- 3 | 30 | inserted by merge
- 3 | 300 | initial
- 4 | 40 | inserted by merge
- 5 | 500 | initial
- 5 | 50 | inserted by merge
- 6 | 60 | inserted by merge
- 7 | 700 | initial
- 7 | 70 | inserted by merge
- 8 | 80 | inserted by merge
- 9 | 90 | inserted by merge
- 9 | 900 | initial
- 10 | 100 | inserted by merge
- 11 | 1100 | initial
- 11 | 110 | inserted by merge
- 12 | 120 | inserted by merge
- 13 | 1300 | initial
- 13 | 130 | inserted by merge
- 14 | 140 | inserted by merge
-(20 rows)
-
-ROLLBACK;
--- try updating the partition key column
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
- tid | balance | val
------+---------+--------------------------
- 2 | 110 | initial updated by merge
- 2 | 20 | inserted by merge
- 4 | 40 | inserted by merge
- 4 | 330 | initial updated by merge
- 6 | 550 | initial updated by merge
- 6 | 60 | inserted by merge
- 8 | 80 | inserted by merge
- 8 | 770 | initial updated by merge
- 10 | 990 | initial updated by merge
- 10 | 100 | inserted by merge
- 12 | 1210 | initial updated by merge
- 12 | 120 | inserted by merge
- 14 | 1430 | initial updated by merge
- 14 | 140 | inserted by merge
-(14 rows)
-
-ROLLBACK;
-DROP TABLE pa_source;
-DROP TABLE pa_target CASCADE;
--- Sub-partitionin
-CREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)
- PARTITION BY RANGE (logts);
-CREATE TABLE part_m01 PARTITION OF pa_target
- FOR VALUES FROM ('2017-01-01') TO ('2017-02-01')
- PARTITION BY LIST (tid);
-CREATE TABLE part_m01_odd PARTITION OF part_m01
- FOR VALUES IN (1,3,5,7,9);
-CREATE TABLE part_m01_even PARTITION OF part_m01
- FOR VALUES IN (2,4,6,8);
-CREATE TABLE part_m02 PARTITION OF pa_target
- FOR VALUES FROM ('2017-02-01') TO ('2017-03-01')
- PARTITION BY LIST (tid);
-CREATE TABLE part_m02_odd PARTITION OF part_m02
- FOR VALUES IN (1,3,5,7,9);
-CREATE TABLE part_m02_even PARTITION OF part_m02
- FOR VALUES IN (2,4,6,8);
-CREATE TABLE pa_source (sid integer, delta float);
--- insert many rows to the source table
-INSERT INTO pa_source SELECT id, id * 10 FROM generate_series(1,14) AS id;
--- insert a few rows in the target table (odd numbered tid)
-INSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;
-INSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;
--- try simple MERGE
-BEGIN;
-MERGE INTO pa_target t
- USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
- logts | tid | balance | val
---------------------------+-----+---------+--------------------------
- Tue Jan 31 00:00:00 2017 | 1 | 110 | initial updated by merge
- Tue Feb 28 00:00:00 2017 | 2 | 220 | initial updated by merge
- Sun Jan 15 00:00:00 2017 | 3 | 30 | inserted by merge
- Tue Jan 31 00:00:00 2017 | 4 | 440 | initial updated by merge
- Tue Feb 28 00:00:00 2017 | 5 | 550 | initial updated by merge
- Sun Jan 15 00:00:00 2017 | 6 | 60 | inserted by merge
- Tue Jan 31 00:00:00 2017 | 7 | 770 | initial updated by merge
- Tue Feb 28 00:00:00 2017 | 8 | 880 | initial updated by merge
- Sun Jan 15 00:00:00 2017 | 9 | 90 | inserted by merge
-(9 rows)
-
-ROLLBACK;
-DROP TABLE pa_source;
-DROP TABLE pa_target CASCADE;
--- some complex joins on the source side
-CREATE TABLE cj_target (tid integer, balance float, val text);
-CREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer);
-CREATE TABLE cj_source2 (sid2 integer, sval text);
-INSERT INTO cj_source1 VALUES (1, 10, 100);
-INSERT INTO cj_source1 VALUES (1, 20, 200);
-INSERT INTO cj_source1 VALUES (2, 20, 300);
-INSERT INTO cj_source1 VALUES (3, 10, 400);
-INSERT INTO cj_source2 VALUES (1, 'initial source2');
-INSERT INTO cj_source2 VALUES (2, 'initial source2');
-INSERT INTO cj_source2 VALUES (3, 'initial source2');
--- source relation is an unalised join
-MERGE INTO cj_target t
-USING cj_source1 s1
- INNER JOIN cj_source2 s2 ON sid1 = sid2
-ON t.tid = sid1
-WHEN NOT MATCHED THEN
- INSERT VALUES (sid1, delta, sval);
--- try accessing columns from either side of the source join
-MERGE INTO cj_target t
-USING cj_source2 s2
- INNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20
-ON t.tid = sid1
-WHEN NOT MATCHED THEN
- INSERT VALUES (sid2, delta, sval)
-WHEN MATCHED THEN
- DELETE;
--- some simple expressions in INSERT targetlist
-MERGE INTO cj_target t
-USING cj_source2 s2
- INNER JOIN cj_source1 s1 ON sid1 = sid2
-ON t.tid = sid1
-WHEN NOT MATCHED THEN
- INSERT VALUES (sid2, delta + scat, sval)
-WHEN MATCHED THEN
- UPDATE SET val = val || ' updated by merge';
-MERGE INTO cj_target t
-USING cj_source2 s2
- INNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20
-ON t.tid = sid1
-WHEN MATCHED THEN
- UPDATE SET val = val || ' ' || delta::text;
-SELECT * FROM cj_target;
- tid | balance | val
------+---------+----------------------------------
- 3 | 400 | initial source2 updated by merge
- 1 | 220 | initial source2 200
- 1 | 110 | initial source2 200
- 2 | 320 | initial source2 300
-(4 rows)
-
-ALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;
-ALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;
-TRUNCATE cj_target;
-MERGE INTO cj_target t
-USING cj_source1 s1
- INNER JOIN cj_source2 s2 ON s1.sid = s2.sid
-ON t.tid = s1.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (s2.sid, delta, sval);
-DROP TABLE cj_source2, cj_source1, cj_target;
--- Function scans
-CREATE TABLE fs_target (a int, b int, c text);
-MERGE INTO fs_target t
-USING generate_series(1,100,1) AS id
-ON t.a = id
-WHEN MATCHED THEN
- UPDATE SET b = b + id
-WHEN NOT MATCHED THEN
- INSERT VALUES (id, -1);
-MERGE INTO fs_target t
-USING generate_series(1,100,2) AS id
-ON t.a = id
-WHEN MATCHED THEN
- UPDATE SET b = b + id, c = 'updated '|| id.*::text
-WHEN NOT MATCHED THEN
- INSERT VALUES (id, -1, 'inserted ' || id.*::text);
-SELECT count(*) FROM fs_target;
- count
--------
- 100
-(1 row)
-
-DROP TABLE fs_target;
--- SERIALIZABLE test
--- handled in isolation tests
--- prepare
-RESET SESSION AUTHORIZATION;
-DROP TABLE target, target2;
-DROP TABLE source, source2;
-DROP FUNCTION merge_trigfunc();
-DROP USER merge_privs;
-DROP USER merge_no_privs;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 864f2c13457..ac8968d24f5 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -517,104 +517,6 @@ SELECT atest6 FROM atest6; -- ok
(0 rows)
COPY atest6 TO stdout; -- ok
--- test column privileges with MERGE
-SET SESSION AUTHORIZATION regress_priv_user1;
-CREATE TABLE mtarget (a int, b text);
-CREATE TABLE msource (a int, b text);
-INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2');
-INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3');
-GRANT SELECT (a) ON msource TO regress_priv_user4;
-GRANT SELECT (a) ON mtarget TO regress_priv_user4;
-GRANT INSERT (a,b) ON mtarget TO regress_priv_user4;
-GRANT UPDATE (b) ON mtarget TO regress_priv_user4;
-SET SESSION AUTHORIZATION regress_priv_user4;
---
--- test source privileges
---
--- fail (no SELECT priv on s.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, NULL);
-ERROR: permission denied for table msource
--- fail (s.b used in the INSERTed values)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = 'x'
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, b);
-ERROR: permission denied for table msource
--- fail (s.b used in the WHEN quals)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND s.b = 'x' THEN
- UPDATE SET b = 'x'
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, NULL);
-ERROR: permission denied for table msource
--- this should be ok since only s.a is accessed
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = 'ok'
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, NULL);
-ROLLBACK;
-SET SESSION AUTHORIZATION regress_priv_user1;
-GRANT SELECT (b) ON msource TO regress_priv_user4;
-SET SESSION AUTHORIZATION regress_priv_user4;
--- should now be ok
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, b);
-ROLLBACK;
---
--- test target privileges
---
--- fail (no SELECT priv on t.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = t.b
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, NULL);
-ERROR: permission denied for table mtarget
--- fail (no UPDATE on t.a)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = s.b, a = t.a + 1
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, b);
-ERROR: permission denied for table mtarget
--- fail (no SELECT on t.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
- UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, b);
-ERROR: permission denied for table mtarget
--- ok
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = s.b;
-ROLLBACK;
--- fail (no DELETE)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
- DELETE;
-ERROR: permission denied for table mtarget
--- grant delete privileges
-SET SESSION AUTHORIZATION regress_priv_user1;
-GRANT DELETE ON mtarget TO regress_priv_user4;
--- should be ok now
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
- DELETE;
-ROLLBACK;
-- check error reporting with column privs
SET SESSION AUTHORIZATION regress_priv_user1;
CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index bf7af3ba826..f1ae40df61c 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -2139,188 +2139,6 @@ INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = '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
--
SET SESSION AUTHORIZATION regress_rls_alice;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index a4ec5690aa2..ae0cd253d5f 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3265,37 +3265,6 @@ CREATE RULE rules_parted_table_insert AS ON INSERT to rules_parted_table
ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
DROP TABLE rules_parted_table;
--
--- test MERGE
---
-CREATE TABLE rule_merge1 (a int, b text);
-CREATE TABLE rule_merge2 (a int, b text);
-CREATE RULE rule1 AS ON INSERT TO rule_merge1
- DO INSTEAD INSERT INTO rule_merge2 VALUES (NEW.*);
-CREATE RULE rule2 AS ON UPDATE TO rule_merge1
- DO INSTEAD UPDATE rule_merge2 SET a = NEW.a, b = NEW.b
- WHERE a = OLD.a;
-CREATE RULE rule3 AS ON DELETE TO rule_merge1
- DO INSTEAD DELETE FROM rule_merge2 WHERE a = OLD.a;
--- MERGE not supported for table with rules
-MERGE INTO rule_merge1 t USING (SELECT 1 AS a) s
- ON t.a = s.a
- WHEN MATCHED AND t.a < 2 THEN
- UPDATE SET b = b || ' updated by merge'
- WHEN MATCHED AND t.a > 2 THEN
- DELETE
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.a, '');
-ERROR: MERGE is not supported for relations with rules
--- should be ok with the other table though
-MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
- ON t.a = s.a
- WHEN MATCHED AND t.a < 2 THEN
- UPDATE SET b = b || ' updated by merge'
- WHEN MATCHED AND t.a > 2 THEN
- DELETE
- WHEN NOT MATCHED THEN
- INSERT VALUES (s.a, '');
---
-- Test enabling/disabling
--
CREATE TABLE ruletest1 (a int);
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index a29b7b1d08d..1f8caef2d7f 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -2784,54 +2784,6 @@ delete from self_ref where a = 1;
NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT
NOTICE: trigger = self_ref_s_trig, old table = (1,), (2,1), (3,2), (4,3)
drop table self_ref;
---
--- test transition tables with MERGE
---
-create table merge_target_table (a int primary key, b text);
-create trigger merge_target_table_insert_trig
- after insert on merge_target_table referencing new table as new_table
- for each statement execute procedure dump_insert();
-create trigger merge_target_table_update_trig
- after update on merge_target_table referencing old table as old_table new table as new_table
- for each statement execute procedure dump_update();
-create trigger merge_target_table_delete_trig
- after delete on merge_target_table referencing old table as old_table
- for each statement execute procedure dump_delete();
-create table merge_source_table (a int, b text);
-insert into merge_source_table
- values (1, 'initial1'), (2, 'initial2'),
- (3, 'initial3'), (4, 'initial4');
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when not matched then
- insert values (a, b);
-NOTICE: trigger = merge_target_table_insert_trig, new table = (1,initial1), (2,initial2), (3,initial3), (4,initial4)
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when matched and s.a <= 2 then
- update set b = t.b || ' updated by merge'
-when matched and s.a > 2 then
- delete
-when not matched then
- insert values (a, b);
-NOTICE: trigger = merge_target_table_delete_trig, old table = (3,initial3), (4,initial4)
-NOTICE: trigger = merge_target_table_update_trig, old table = (1,initial1), (2,initial2), new table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge")
-NOTICE: trigger = merge_target_table_insert_trig, new table = <NULL>
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when matched and s.a <= 2 then
- update set b = t.b || ' updated again by merge'
-when matched and s.a > 2 then
- delete
-when not matched then
- insert values (a, b);
-NOTICE: trigger = merge_target_table_delete_trig, old table = <NULL>
-NOTICE: trigger = merge_target_table_update_trig, old table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge"), new table = (1,"initial1 updated by merge updated again by merge"), (2,"initial2 updated by merge updated again by merge")
-NOTICE: trigger = merge_target_table_insert_trig, new table = (3,initial3), (4,initial4)
-drop table merge_source_table, merge_target_table;
-- cleanup
drop function dump_insert();
drop function dump_update();
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 350a34d9870..2a2085556bb 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -1904,143 +1904,6 @@ RETURNING k, v;
(0 rows)
DROP TABLE withz;
--- WITH referenced by MERGE statement
-CREATE TABLE m AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i;
-ALTER TABLE m ADD UNIQUE (k);
-WITH RECURSIVE cte_basic AS (SELECT 1 a, 'cte_basic val' b)
-MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
-ERROR: WITH RECURSIVE is not supported for MERGE statement
--- Basic:
-WITH cte_basic AS (SELECT 1 a, 'cte_basic val' b)
-MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
--- Examine
-SELECT * FROM m where k = 0;
- k | v
----+----------------------
- 0 | merge source SubPlan
-(1 row)
-
--- See EXPLAIN output for same query:
-EXPLAIN (VERBOSE, COSTS OFF)
-WITH cte_basic AS (SELECT 1 a, 'cte_basic val' b)
-MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
- QUERY PLAN
--------------------------------------------------------------------
- Merge on public.m
- CTE cte_basic
- -> Result
- Output: 1, 'cte_basic val'::text
- -> Hash Right Join
- Output: o.k, o.v, o.*, m_1.ctid
- Hash Cond: (m_1.k = o.k)
- -> Seq Scan on public.m m_1
- Output: m_1.ctid, m_1.k
- -> Hash
- Output: o.k, o.v, o.*
- -> Subquery Scan on o
- Output: o.k, o.v, o.*
- -> Result
- Output: 0, 'merge source SubPlan'::text
- SubPlan 2
- -> Limit
- Output: ((cte_basic.b || ' merge update'::text))
- -> CTE Scan on cte_basic
- Output: (cte_basic.b || ' merge update'::text)
- Filter: (cte_basic.a = m.k)
-(21 rows)
-
--- InitPlan
-WITH cte_init AS (SELECT 1 a, 'cte_init val' b)
-MERGE INTO m USING (select 1 k, 'merge source InitPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_init WHERE a = 1 LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
--- Examine
-SELECT * FROM m where k = 1;
- k | v
----+---------------------------
- 1 | cte_init val merge update
-(1 row)
-
--- See EXPLAIN output for same query:
-EXPLAIN (VERBOSE, COSTS OFF)
-WITH cte_init AS (SELECT 1 a, 'cte_init val' b)
-MERGE INTO m USING (select 1 k, 'merge source InitPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_init WHERE a = 1 LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
- QUERY PLAN
---------------------------------------------------------------------
- Merge on public.m
- CTE cte_init
- -> Result
- Output: 1, 'cte_init val'::text
- InitPlan 2 (returns $1)
- -> Limit
- Output: ((cte_init.b || ' merge update'::text))
- -> CTE Scan on cte_init
- Output: (cte_init.b || ' merge update'::text)
- Filter: (cte_init.a = 1)
- -> Hash Right Join
- Output: o.k, o.v, o.*, m_1.ctid
- Hash Cond: (m_1.k = o.k)
- -> Seq Scan on public.m m_1
- Output: m_1.ctid, m_1.k
- -> Hash
- Output: o.k, o.v, o.*
- -> Subquery Scan on o
- Output: o.k, o.v, o.*
- -> Result
- Output: 1, 'merge source InitPlan'::text
-(21 rows)
-
--- MERGE source comes from CTE:
-WITH merge_source_cte AS (SELECT 15 a, 'merge_source_cte val' b)
-MERGE INTO m USING (select * from merge_source_cte) o ON m.k=o.a
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || merge_source_cte.*::text || ' merge update' FROM merge_source_cte WHERE a = 15)
-WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text || ' merge insert' FROM merge_source_cte));
--- Examine
-SELECT * FROM m where k = 15;
- k | v
-----+--------------------------------------------------------------
- 15 | merge_source_cte val(15,"merge_source_cte val") merge insert
-(1 row)
-
--- See EXPLAIN output for same query:
-EXPLAIN (VERBOSE, COSTS OFF)
-WITH merge_source_cte AS (SELECT 15 a, 'merge_source_cte val' b)
-MERGE INTO m USING (select * from merge_source_cte) o ON m.k=o.a
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || merge_source_cte.*::text || ' merge update' FROM merge_source_cte WHERE a = 15)
-WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text || ' merge insert' FROM merge_source_cte));
- QUERY PLAN
----------------------------------------------------------------------------------------------------------------
- Merge on public.m
- CTE merge_source_cte
- -> Result
- Output: 15, 'merge_source_cte val'::text
- InitPlan 2 (returns $1)
- -> CTE Scan on merge_source_cte merge_source_cte_1
- Output: ((merge_source_cte_1.b || (merge_source_cte_1.*)::text) || ' merge update'::text)
- Filter: (merge_source_cte_1.a = 15)
- InitPlan 3 (returns $2)
- -> CTE Scan on merge_source_cte merge_source_cte_2
- Output: ((merge_source_cte_2.*)::text || ' merge insert'::text)
- -> Hash Right Join
- Output: merge_source_cte.a, merge_source_cte.b, ROW(merge_source_cte.a, merge_source_cte.b), m_1.ctid
- Hash Cond: (m_1.k = merge_source_cte.a)
- -> Seq Scan on public.m m_1
- Output: m_1.ctid, m_1.k
- -> Hash
- Output: merge_source_cte.a, merge_source_cte.b
- -> CTE Scan on merge_source_cte
- Output: merge_source_cte.a, merge_source_cte.b
-(20 rows)
-
-DROP TABLE m;
-- check that run to completion happens in proper ordering
TRUNCATE TABLE y;
INSERT INTO y SELECT generate_series(1, 3);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 0d3a27ed410..fbc0b4b7293 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index merge
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 20027c131c2..4443be5b665 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -124,7 +124,6 @@ test: tablesample
test: groupingsets
test: drop_operator
test: password
-test: merge
test: alter_generic
test: alter_operator
test: misc
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index f8f34eaf185..a35f331f4e0 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -246,48 +246,3 @@ CREATE TABLE itest_child PARTITION OF itest_parent (
f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
DROP TABLE itest_parent;
-
--- MERGE tests
-CREATE TABLE itest14 (a int GENERATED ALWAYS AS IDENTITY, b text);
-CREATE TABLE itest15 (a int GENERATED BY DEFAULT AS IDENTITY, b text);
-
-MERGE INTO itest14 t
-USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest14 t
-USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest14 t
-USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest15 t
-USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest15 t
-USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
-
-MERGE INTO itest15 t
-USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
-ON t.a = s.s_a
-WHEN NOT MATCHED THEN
- INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
-
-SELECT * FROM itest14;
-SELECT * FROM itest15;
-DROP TABLE itest14;
-DROP TABLE itest15;
diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql
deleted file mode 100644
index f6ef6a9acae..00000000000
--- a/src/test/regress/sql/merge.sql
+++ /dev/null
@@ -1,1173 +0,0 @@
---
--- MERGE
---
---\set VERBOSITY verbose
-
---set debug_print_rewritten = true;
---set debug_print_parse = true;
---set debug_print_pretty = true;
-
-
-CREATE USER merge_privs;
-CREATE USER merge_no_privs;
-DROP TABLE IF EXISTS target;
-DROP TABLE IF EXISTS source;
-CREATE TABLE target (tid integer, balance integer);
-CREATE TABLE source (sid integer, delta integer); --no index
-INSERT INTO target VALUES (1, 10);
-INSERT INTO target VALUES (2, 20);
-INSERT INTO target VALUES (3, 30);
-SELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;
-
-ALTER TABLE target OWNER TO merge_privs;
-ALTER TABLE source OWNER TO merge_privs;
-
-CREATE TABLE target2 (tid integer, balance integer);
-CREATE TABLE source2 (sid integer, delta integer);
-
-ALTER TABLE target2 OWNER TO merge_no_privs;
-ALTER TABLE source2 OWNER TO merge_no_privs;
-
-GRANT INSERT ON target TO merge_no_privs;
-
-SET SESSION AUTHORIZATION merge_privs;
-
-EXPLAIN (COSTS OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-
---
--- Errors
---
-MERGE INTO target t RANDOMWORD
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
--- MATCHED/INSERT error
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- INSERT DEFAULT VALUES
-;
--- incorrectly specifying INTO target
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT INTO target DEFAULT VALUES
-;
--- Multiple VALUES clause
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (1,1), (2,2);
-;
--- SELECT query for INSERT
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT SELECT (1, 1);
-;
--- NOT MATCHED/UPDATE
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- UPDATE SET balance = 0
-;
--- UPDATE tablename
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE target SET balance = 0
-;
-
--- unsupported relation types
--- view
-CREATE VIEW tv AS SELECT * FROM target;
-MERGE INTO tv t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-DROP VIEW tv;
-
--- materialized view
-CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
-MERGE INTO mv t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-DROP MATERIALIZED VIEW mv;
-
--- inherited table
-CREATE TABLE inhp (tid int, balance int);
-CREATE TABLE child1() INHERITS (inhp);
-CREATE TABLE child2() INHERITS (child1);
-
-MERGE INTO inhp t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-
-MERGE INTO child1 t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-
--- this should be ok
-MERGE INTO child2 t
-USING source s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES;
-DROP TABLE inhp, child1, child2;
-
--- permissions
-
-MERGE INTO target
-USING source2
-ON target.tid = source2.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-
-GRANT INSERT ON target TO merge_no_privs;
-SET SESSION AUTHORIZATION merge_no_privs;
-
-MERGE INTO target
-USING source2
-ON target.tid = source2.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-
-GRANT UPDATE ON target2 TO merge_privs;
-SET SESSION AUTHORIZATION merge_privs;
-
-MERGE INTO target2
-USING source
-ON target2.tid = source.sid
-WHEN MATCHED THEN
- DELETE
-;
-
-MERGE INTO target2
-USING source
-ON target2.tid = source.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES
-;
-
--- check if the target can be accessed from source relation subquery; we should
--- not be able to do so
-MERGE INTO target t
-USING (SELECT * FROM source WHERE t.tid > sid) s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES
-;
-
---
--- initial tests
---
--- zero rows in source has no effect
-MERGE INTO target
-USING source
-ON target.tid = source.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES
-;
-ROLLBACK;
-
--- insert some non-matching source rows to work from
-INSERT INTO source VALUES (4, 40);
-SELECT * FROM source ORDER BY sid;
-SELECT * FROM target ORDER BY tid;
-
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- DO NOTHING
-;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT DEFAULT VALUES
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- index plans
-INSERT INTO target SELECT generate_series(1000,2500), 0;
-ALTER TABLE target ADD PRIMARY KEY (tid);
-ANALYZE target;
-
-EXPLAIN (COSTS OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-EXPLAIN (COSTS OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-EXPLAIN (COSTS OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (4, NULL);
-;
-DELETE FROM target WHERE tid > 100;
-ANALYZE target;
-
--- insert some matching source rows to work from
-INSERT INTO source VALUES (2, 5);
-INSERT INTO source VALUES (3, 20);
-SELECT * FROM source ORDER BY sid;
-SELECT * FROM target ORDER BY tid;
-
--- equivalent of an UPDATE join
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- equivalent of a DELETE join
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (4, NULL)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- duplicate source row causes multiple target row update ERROR
-INSERT INTO source VALUES (2, 5);
-SELECT * FROM source ORDER BY sid;
-SELECT * FROM target ORDER BY tid;
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-ROLLBACK;
-
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- DELETE
-;
-ROLLBACK;
-
--- correct source data
-DELETE FROM source WHERE sid = 2;
-INSERT INTO source VALUES (2, 5);
-SELECT * FROM source ORDER BY sid;
-SELECT * FROM target ORDER BY tid;
-
--- remove constraints
-alter table target drop CONSTRAINT target_pkey;
-alter table target alter column tid drop not null;
-
--- multiple actions
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (4, 4)
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- should be equivalent
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = 0
-WHEN NOT MATCHED THEN
- INSERT VALUES (4, 4);
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- column references
--- do a simple equivalent of an UPDATE join
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = t.balance + s.delta
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- do a simple equivalent of an INSERT SELECT
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- and again with explicitly identified column list
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- and again with a subtle error: referring to non-existent target row for NOT MATCHED
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (t.tid, s.delta)
-;
-
--- and again with a constant ON clause
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON (SELECT true)
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (t.tid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- now the classic UPSERT
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = t.balance + s.delta
-WHEN NOT MATCHED THEN
- INSERT VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- unreachable WHEN clause should ERROR
-BEGIN;
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN /* Terminal WHEN clause for MATCHED */
- DELETE
-WHEN MATCHED AND s.delta > 0 THEN
- UPDATE SET balance = t.balance - s.delta
-;
-ROLLBACK;
-
--- conditional WHEN clause
-CREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1);
-CREATE TABLE wq_source (balance integer, sid integer);
-
-INSERT INTO wq_source (sid, balance) VALUES (1, 100);
-
-BEGIN;
--- try a simple INSERT with default values first
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
-ROLLBACK;
-
--- this time with a FALSE condition
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND FALSE THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
-
--- this time with an actual condition which returns false
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND s.balance <> 100 THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
-
-BEGIN;
--- and now with a condition which returns true
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND s.balance = 100 THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
-ROLLBACK;
-
--- conditions in the NOT MATCHED clause can only refer to source columns
-BEGIN;
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND t.balance = 100 THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
-ROLLBACK;
-
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN NOT MATCHED AND s.balance = 100 THEN
- INSERT (tid) VALUES (s.sid);
-SELECT * FROM wq_target;
-
--- conditions in MATCHED clause can refer to both source and target
-SELECT * FROM wq_source;
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND s.balance = 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
-
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
-
--- check if AND works
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
-
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
-
--- check if OR works
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
-
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
-
--- check if subqueries work in the conditions?
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN
- UPDATE SET balance = t.balance + s.balance;
-
--- check if we can access system columns in the conditions
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.xmin = t.xmax THEN
- UPDATE SET balance = t.balance + s.balance;
-
-ALTER TABLE wq_target SET WITH OIDS;
-SELECT * FROM wq_target;
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND t.oid >= 0 THEN
- UPDATE SET balance = t.balance + s.balance;
-SELECT * FROM wq_target;
-
--- test preventing WHEN AND conditions from writing to the database
-create or replace function merge_when_and_write() returns boolean
-language plpgsql as
-$$
-BEGIN
- INSERT INTO target VALUES (100, 100);
- RETURN TRUE;
-END;
-$$;
-
-BEGIN;
-MERGE INTO wq_target t
-USING wq_source s ON t.tid = s.sid
-WHEN MATCHED AND (merge_when_and_write()) THEN
- UPDATE SET balance = t.balance + s.balance;
-ROLLBACK;
-drop function merge_when_and_write();
-
-DROP TABLE wq_target, wq_source;
-
--- test triggers
-create or replace function merge_trigfunc () returns trigger
-language plpgsql as
-$$
-BEGIN
- RAISE NOTICE '% % % trigger', TG_WHEN, TG_OP, TG_LEVEL;
- IF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN
- IF (TG_OP = 'DELETE') THEN
- RETURN OLD;
- ELSE
- RETURN NEW;
- END IF;
- ELSE
- RETURN NULL;
- END IF;
-END;
-$$;
-CREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-CREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();
-
--- now the classic UPSERT, with a DELETE
-BEGIN;
-UPDATE target SET balance = 0 WHERE tid = 3;
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED AND t.balance > s.delta THEN
- UPDATE SET balance = t.balance - s.delta
-WHEN MATCHED THEN
- DELETE
-WHEN NOT MATCHED THEN
- INSERT VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- test from PL/pgSQL
--- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO
-BEGIN;
-DO LANGUAGE plpgsql $$
-BEGIN
-MERGE INTO target t
-USING source AS s
-ON t.tid = s.sid
-WHEN MATCHED AND t.balance > s.delta THEN
- UPDATE SET balance = t.balance - s.delta
-;
-END;
-$$;
-ROLLBACK;
-
---source constants
-BEGIN;
-MERGE INTO target t
-USING (SELECT 9 AS sid, 57 AS delta) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
---source query
-BEGIN;
-MERGE INTO target t
-USING (SELECT sid, delta FROM source WHERE delta > 0) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
-BEGIN;
-MERGE INTO target t
-USING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.newname)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
---self-merge
-BEGIN;
-MERGE INTO target t1
-USING target t2
-ON t1.tid = t2.tid
-WHEN MATCHED THEN
- UPDATE SET balance = t1.balance + t2.balance
-WHEN NOT MATCHED THEN
- INSERT VALUES (t2.tid, t2.balance)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
-BEGIN;
-MERGE INTO target t
-USING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
-BEGIN;
-MERGE INTO target t
-USING
-(SELECT sid, max(delta) AS delta
- FROM source
- GROUP BY sid
- HAVING count(*) = 1
- ORDER BY sid ASC) AS s
-ON t.tid = s.sid
-WHEN NOT MATCHED THEN
- INSERT (tid, balance) VALUES (s.sid, s.delta)
-;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- plpgsql parameters and results
-BEGIN;
-CREATE FUNCTION merge_func (p_id integer, p_bal integer)
-RETURNS INTEGER
-LANGUAGE plpgsql
-AS $$
-DECLARE
- result integer;
-BEGIN
-MERGE INTO target t
-USING (SELECT p_id AS sid) AS s
-ON t.tid = s.sid
-WHEN MATCHED THEN
- UPDATE SET balance = t.balance - p_bal
-;
-IF FOUND THEN
- GET DIAGNOSTICS result := ROW_COUNT;
-END IF;
-RETURN result;
-END;
-$$;
-SELECT merge_func(3, 4);
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- PREPARE
-BEGIN;
-prepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;
-execute foom;
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
-BEGIN;
-PREPARE foom2 (integer, integer) AS
-MERGE INTO target t
-USING (SELECT 1) s
-ON t.tid = $1
-WHEN MATCHED THEN
-UPDATE SET balance = $2;
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-execute foom2 (1, 1);
-SELECT * FROM target ORDER BY tid;
-ROLLBACK;
-
--- subqueries in source relation
-
-CREATE TABLE sq_target (tid integer NOT NULL, balance integer);
-CREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0);
-
-INSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);
-INSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);
-
-BEGIN;
-MERGE INTO sq_target t
-USING (SELECT * FROM sq_source) s
-ON tid = sid
-WHEN MATCHED AND t.balance > delta THEN
- UPDATE SET balance = t.balance + delta;
-SELECT * FROM sq_target;
-ROLLBACK;
-
--- try a view
-CREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;
-
-BEGIN;
-MERGE INTO sq_target
-USING v
-ON tid = sid
-WHEN MATCHED THEN
- UPDATE SET balance = v.balance + delta;
-SELECT * FROM sq_target;
-ROLLBACK;
-
--- ambiguous reference to a column
-BEGIN;
-MERGE INTO sq_target
-USING v
-ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
- UPDATE SET balance = balance + delta
-WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
-WHEN MATCHED AND tid < 2 THEN
- DELETE;
-ROLLBACK;
-
-BEGIN;
-INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
- UPDATE SET balance = t.balance + delta
-WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
-WHEN MATCHED AND tid < 2 THEN
- DELETE;
-SELECT * FROM sq_target;
-ROLLBACK;
-
--- CTEs
-BEGIN;
-INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
-WITH targq AS (
- SELECT * FROM v
-)
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
- UPDATE SET balance = t.balance + delta
-WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
-WHEN MATCHED AND tid < 2 THEN
- DELETE
-;
-ROLLBACK;
-
--- RETURNING
-BEGIN;
-INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
- UPDATE SET balance = t.balance + delta
-WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
-WHEN MATCHED AND tid < 2 THEN
- DELETE
-RETURNING *
-;
-ROLLBACK;
-
--- EXPLAIN
-CREATE TABLE ex_mtarget (a int, b int);
-CREATE TABLE ex_msource (a int, b int);
-INSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;
-INSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;
-
--- only updates
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = t.b + 1;
-
--- only updates to selected tuples
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN MATCHED AND t.a < 10 THEN
- UPDATE SET b = t.b + 1;
-
--- updates + deletes
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN MATCHED AND t.a < 10 THEN
- UPDATE SET b = t.b + 1
-WHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN
- DELETE;
-
--- only inserts
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN NOT MATCHED AND s.a < 10 THEN
- INSERT VALUES (a, b);
-
--- all three
---EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF)
-MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
-WHEN MATCHED AND t.a < 10 THEN
- UPDATE SET b = t.b + 1
-WHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN
- DELETE
-WHEN NOT MATCHED AND s.a < 20 THEN
- INSERT VALUES (a, b);
-
-DROP TABLE ex_msource, ex_mtarget;
-
--- Subqueries
-BEGIN;
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED THEN
- UPDATE SET balance = (SELECT count(*) FROM sq_target)
-;
-SELECT * FROM sq_target WHERE tid = 1;
-ROLLBACK;
-
-BEGIN;
-MERGE INTO sq_target t
-USING v
-ON tid = sid
-WHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN
- UPDATE SET balance = 42
-;
-SELECT * FROM sq_target WHERE tid = 1;
-ROLLBACK;
-
-BEGIN;
-MERGE INTO sq_target t
-USING v
-ON tid = sid AND (SELECT count(*) > 0 FROM sq_target)
-WHEN MATCHED THEN
- UPDATE SET balance = 42
-;
-SELECT * FROM sq_target WHERE tid = 1;
-ROLLBACK;
-
-DROP TABLE sq_target, sq_source CASCADE;
-
-CREATE TABLE pa_target (tid integer, balance float, val text)
- PARTITION BY LIST (tid);
-
-CREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4);
-CREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6);
-CREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9);
-CREATE TABLE part4 PARTITION OF pa_target DEFAULT;
-
-CREATE TABLE pa_source (sid integer, delta float);
--- insert many rows to the source table
-INSERT INTO pa_source SELECT id, id * 10 FROM generate_series(1,14) AS id;
--- insert a few rows in the target table (odd numbered tid)
-INSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;
-
--- try simple MERGE
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
-ROLLBACK;
-
--- same with a constant qual
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid AND tid = 1
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
-ROLLBACK;
-
--- try updating the partition key column
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
-ROLLBACK;
-
-DROP TABLE pa_target CASCADE;
-
--- The target table is partitioned in the same way, but this time by attaching
--- partitions which have columns in different order, dropped columns etc.
-CREATE TABLE pa_target (tid integer, balance float, val text)
- PARTITION BY LIST (tid);
-CREATE TABLE part1 (tid integer, balance float, val text);
-CREATE TABLE part2 (balance float, tid integer, val text);
-CREATE TABLE part3 (tid integer, balance float, val text);
-CREATE TABLE part4 (extraid text, tid integer, balance float, val text);
-ALTER TABLE part4 DROP COLUMN extraid;
-
-ALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);
-ALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);
-ALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);
-ALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;
-
--- insert a few rows in the target table (odd numbered tid)
-INSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;
-
--- try simple MERGE
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
-ROLLBACK;
-
--- same with a constant qual
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid AND tid = 1
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
-ROLLBACK;
-
--- try updating the partition key column
-BEGIN;
-MERGE INTO pa_target t
- USING pa_source s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
-ROLLBACK;
-
-DROP TABLE pa_source;
-DROP TABLE pa_target CASCADE;
-
--- Sub-partitionin
-CREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)
- PARTITION BY RANGE (logts);
-
-CREATE TABLE part_m01 PARTITION OF pa_target
- FOR VALUES FROM ('2017-01-01') TO ('2017-02-01')
- PARTITION BY LIST (tid);
-CREATE TABLE part_m01_odd PARTITION OF part_m01
- FOR VALUES IN (1,3,5,7,9);
-CREATE TABLE part_m01_even PARTITION OF part_m01
- FOR VALUES IN (2,4,6,8);
-CREATE TABLE part_m02 PARTITION OF pa_target
- FOR VALUES FROM ('2017-02-01') TO ('2017-03-01')
- PARTITION BY LIST (tid);
-CREATE TABLE part_m02_odd PARTITION OF part_m02
- FOR VALUES IN (1,3,5,7,9);
-CREATE TABLE part_m02_even PARTITION OF part_m02
- FOR VALUES IN (2,4,6,8);
-
-CREATE TABLE pa_source (sid integer, delta float);
--- insert many rows to the source table
-INSERT INTO pa_source SELECT id, id * 10 FROM generate_series(1,14) AS id;
--- insert a few rows in the target table (odd numbered tid)
-INSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;
-INSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;
-
--- try simple MERGE
-BEGIN;
-MERGE INTO pa_target t
- USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s
- ON t.tid = s.sid
- WHEN MATCHED THEN
- UPDATE SET balance = balance + delta, val = val || ' updated by merge'
- WHEN NOT MATCHED THEN
- INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');
-SELECT * FROM pa_target ORDER BY tid;
-ROLLBACK;
-
-DROP TABLE pa_source;
-DROP TABLE pa_target CASCADE;
-
--- some complex joins on the source side
-
-CREATE TABLE cj_target (tid integer, balance float, val text);
-CREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer);
-CREATE TABLE cj_source2 (sid2 integer, sval text);
-INSERT INTO cj_source1 VALUES (1, 10, 100);
-INSERT INTO cj_source1 VALUES (1, 20, 200);
-INSERT INTO cj_source1 VALUES (2, 20, 300);
-INSERT INTO cj_source1 VALUES (3, 10, 400);
-INSERT INTO cj_source2 VALUES (1, 'initial source2');
-INSERT INTO cj_source2 VALUES (2, 'initial source2');
-INSERT INTO cj_source2 VALUES (3, 'initial source2');
-
--- source relation is an unalised join
-MERGE INTO cj_target t
-USING cj_source1 s1
- INNER JOIN cj_source2 s2 ON sid1 = sid2
-ON t.tid = sid1
-WHEN NOT MATCHED THEN
- INSERT VALUES (sid1, delta, sval);
-
--- try accessing columns from either side of the source join
-MERGE INTO cj_target t
-USING cj_source2 s2
- INNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20
-ON t.tid = sid1
-WHEN NOT MATCHED THEN
- INSERT VALUES (sid2, delta, sval)
-WHEN MATCHED THEN
- DELETE;
-
--- some simple expressions in INSERT targetlist
-MERGE INTO cj_target t
-USING cj_source2 s2
- INNER JOIN cj_source1 s1 ON sid1 = sid2
-ON t.tid = sid1
-WHEN NOT MATCHED THEN
- INSERT VALUES (sid2, delta + scat, sval)
-WHEN MATCHED THEN
- UPDATE SET val = val || ' updated by merge';
-
-MERGE INTO cj_target t
-USING cj_source2 s2
- INNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20
-ON t.tid = sid1
-WHEN MATCHED THEN
- UPDATE SET val = val || ' ' || delta::text;
-
-SELECT * FROM cj_target;
-
-ALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;
-ALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;
-
-TRUNCATE cj_target;
-
-MERGE INTO cj_target t
-USING cj_source1 s1
- INNER JOIN cj_source2 s2 ON s1.sid = s2.sid
-ON t.tid = s1.sid
-WHEN NOT MATCHED THEN
- INSERT VALUES (s2.sid, delta, sval);
-
-DROP TABLE cj_source2, cj_source1, cj_target;
-
--- Function scans
-CREATE TABLE fs_target (a int, b int, c text);
-MERGE INTO fs_target t
-USING generate_series(1,100,1) AS id
-ON t.a = id
-WHEN MATCHED THEN
- UPDATE SET b = b + id
-WHEN NOT MATCHED THEN
- INSERT VALUES (id, -1);
-
-MERGE INTO fs_target t
-USING generate_series(1,100,2) AS id
-ON t.a = id
-WHEN MATCHED THEN
- UPDATE SET b = b + id, c = 'updated '|| id.*::text
-WHEN NOT MATCHED THEN
- INSERT VALUES (id, -1, 'inserted ' || id.*::text);
-
-SELECT count(*) FROM fs_target;
-DROP TABLE fs_target;
-
--- SERIALIZABLE test
--- handled in isolation tests
-
--- prepare
-
-RESET SESSION AUTHORIZATION;
-DROP TABLE target, target2;
-DROP TABLE source, source2;
-DROP FUNCTION merge_trigfunc();
-DROP USER merge_privs;
-DROP USER merge_no_privs;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 0a8abf20769..f7f3bbbeeb6 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -349,114 +349,6 @@ UPDATE atest5 SET one = 1; -- fail
SELECT atest6 FROM atest6; -- ok
COPY atest6 TO stdout; -- ok
--- test column privileges with MERGE
-SET SESSION AUTHORIZATION regress_priv_user1;
-CREATE TABLE mtarget (a int, b text);
-CREATE TABLE msource (a int, b text);
-INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2');
-INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3');
-
-GRANT SELECT (a) ON msource TO regress_priv_user4;
-GRANT SELECT (a) ON mtarget TO regress_priv_user4;
-GRANT INSERT (a,b) ON mtarget TO regress_priv_user4;
-GRANT UPDATE (b) ON mtarget TO regress_priv_user4;
-
-SET SESSION AUTHORIZATION regress_priv_user4;
-
---
--- test source privileges
---
-
--- fail (no SELECT priv on s.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, NULL);
-
--- fail (s.b used in the INSERTed values)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = 'x'
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, b);
-
--- fail (s.b used in the WHEN quals)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND s.b = 'x' THEN
- UPDATE SET b = 'x'
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, NULL);
-
--- this should be ok since only s.a is accessed
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = 'ok'
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, NULL);
-ROLLBACK;
-
-SET SESSION AUTHORIZATION regress_priv_user1;
-GRANT SELECT (b) ON msource TO regress_priv_user4;
-SET SESSION AUTHORIZATION regress_priv_user4;
-
--- should now be ok
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, b);
-ROLLBACK;
-
---
--- test target privileges
---
-
--- fail (no SELECT priv on t.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = t.b
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, NULL);
-
--- fail (no UPDATE on t.a)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = s.b, a = t.a + 1
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, b);
-
--- fail (no SELECT on t.b)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
- UPDATE SET b = s.b
-WHEN NOT MATCHED THEN
- INSERT VALUES (a, b);
-
--- ok
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED THEN
- UPDATE SET b = s.b;
-ROLLBACK;
-
--- fail (no DELETE)
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
- DELETE;
-
--- grant delete privileges
-SET SESSION AUTHORIZATION regress_priv_user1;
-GRANT DELETE ON mtarget TO regress_priv_user4;
--- should be ok now
-BEGIN;
-MERGE INTO mtarget t USING msource s ON t.a = s.a
-WHEN MATCHED AND t.b IS NOT NULL THEN
- DELETE;
-ROLLBACK;
-
-- check error reporting with column privs
SET SESSION AUTHORIZATION regress_priv_user1;
CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index 6c752089980..f3a31dbee03 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -813,162 +813,6 @@ INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = '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
--
SET SESSION AUTHORIZATION regress_rls_alice;
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index b866268892f..a82f52d1548 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1192,39 +1192,6 @@ ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parte
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
--
CREATE TABLE ruletest1 (a int);
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index b08b83bf5fa..7cfa5fdf92a 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2125,53 +2125,6 @@ delete from self_ref where a = 1;
drop table self_ref;
---
--- test transition tables with MERGE
---
-create table merge_target_table (a int primary key, b text);
-create trigger merge_target_table_insert_trig
- after insert on merge_target_table referencing new table as new_table
- for each statement execute procedure dump_insert();
-create trigger merge_target_table_update_trig
- after update on merge_target_table referencing old table as old_table new table as new_table
- for each statement execute procedure dump_update();
-create trigger merge_target_table_delete_trig
- after delete on merge_target_table referencing old table as old_table
- for each statement execute procedure dump_delete();
-
-create table merge_source_table (a int, b text);
-insert into merge_source_table
- values (1, 'initial1'), (2, 'initial2'),
- (3, 'initial3'), (4, 'initial4');
-
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when not matched then
- insert values (a, b);
-
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when matched and s.a <= 2 then
- update set b = t.b || ' updated by merge'
-when matched and s.a > 2 then
- delete
-when not matched then
- insert values (a, b);
-
-merge into merge_target_table t
-using merge_source_table s
-on t.a = s.a
-when matched and s.a <= 2 then
- update set b = t.b || ' updated again by merge'
-when matched and s.a > 2 then
- delete
-when not matched then
- insert values (a, b);
-
-drop table merge_source_table, merge_target_table;
-
-- cleanup
drop function dump_insert();
drop function dump_update();
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index c6b197c3275..f85645efdee 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -862,62 +862,6 @@ RETURNING k, v;
DROP TABLE withz;
--- WITH referenced by MERGE statement
-CREATE TABLE m AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i;
-ALTER TABLE m ADD UNIQUE (k);
-
-WITH RECURSIVE cte_basic AS (SELECT 1 a, 'cte_basic val' b)
-MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
-
--- Basic:
-WITH cte_basic AS (SELECT 1 a, 'cte_basic val' b)
-MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
--- Examine
-SELECT * FROM m where k = 0;
-
--- See EXPLAIN output for same query:
-EXPLAIN (VERBOSE, COSTS OFF)
-WITH cte_basic AS (SELECT 1 a, 'cte_basic val' b)
-MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
-
--- InitPlan
-WITH cte_init AS (SELECT 1 a, 'cte_init val' b)
-MERGE INTO m USING (select 1 k, 'merge source InitPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_init WHERE a = 1 LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
--- Examine
-SELECT * FROM m where k = 1;
-
--- See EXPLAIN output for same query:
-EXPLAIN (VERBOSE, COSTS OFF)
-WITH cte_init AS (SELECT 1 a, 'cte_init val' b)
-MERGE INTO m USING (select 1 k, 'merge source InitPlan' v) o ON m.k=o.k
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_init WHERE a = 1 LIMIT 1)
-WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
-
--- MERGE source comes from CTE:
-WITH merge_source_cte AS (SELECT 15 a, 'merge_source_cte val' b)
-MERGE INTO m USING (select * from merge_source_cte) o ON m.k=o.a
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || merge_source_cte.*::text || ' merge update' FROM merge_source_cte WHERE a = 15)
-WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text || ' merge insert' FROM merge_source_cte));
--- Examine
-SELECT * FROM m where k = 15;
-
--- See EXPLAIN output for same query:
-EXPLAIN (VERBOSE, COSTS OFF)
-WITH merge_source_cte AS (SELECT 15 a, 'merge_source_cte val' b)
-MERGE INTO m USING (select * from merge_source_cte) o ON m.k=o.a
-WHEN MATCHED THEN UPDATE SET v = (SELECT b || merge_source_cte.*::text || ' merge update' FROM merge_source_cte WHERE a = 15)
-WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text || ' merge insert' FROM merge_source_cte));
-
-DROP TABLE m;
-
-- check that run to completion happens in proper ordering
TRUNCATE TABLE y;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6d8a44cd9e8..abc10a8ffd4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1228,8 +1228,6 @@ MemoryContextCallbackFunction
MemoryContextCounters
MemoryContextData
MemoryContextMethods
-MergeAction
-MergeActionState
MergeAppend
MergeAppendPath
MergeAppendState
@@ -1237,7 +1235,6 @@ MergeJoin
MergeJoinClause
MergeJoinState
MergePath
-MergeStmt
MergeScanSelCache
MetaCommand
MinMaxAggInfo