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;
/* display results */
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'include-sequences', '0');
+-- 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;
<structname>PGresult</structname>. This function can only be used following
the execution of a <command>SELECT</command>, <command>CREATE TABLE AS</command>,
<command>INSERT</command>, <command>UPDATE</command>, <command>DELETE</command>,
- <command>MOVE</command>, <command>FETCH</command>, or <command>COPY</command> statement,
- or an <command>EXECUTE</command> of a prepared query that contains an
- <command>INSERT</command>, <command>UPDATE</command>, or <command>DELETE</command> statement.
+ <command>MERGE</command>, <command>MOVE</command>, <command>FETCH</command>,
+ or <command>COPY</command> statement, or an <command>EXECUTE</command> of a
+ prepared query that contains an <command>INSERT</command>,
+ <command>UPDATE</command>, <command>DELETE</command>,
+ or <command>MERGE</command> statement.
If the command that generated the <structname>PGresult</structname> was anything
else, <xref linkend="libpq-PQcmdTuples"/> returns an empty string. The caller
should not free the return value directly. It will be freed when
<literal>11</literal>, which no longer matches the criteria.
</para>
+ <para>
+ <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> or
+ <command>UPDATE</command> will occur.
+ If MERGE attempts an <command>UPDATE</command> or
+ <command>DELETE</command> and the row is concurrently updated but
+ the join condition still passes for the current target and the
+ current source tuple, then <command>MERGE</command> will behave
+ the same as the <command>UPDATE</command> or
+ <command>DELETE</command> commands and perform its action on the
+ updated version of the row. However, because <command>MERGE</command>
+ can specify several actions and they can be conditional, the
+ conditions for each action are re-evaluated on the updated version of
+ the row, starting from the first action, even if the action that had
+ originally matched appears later in the list of actions.
+ On the other hand, if the row is concurrently updated or deleted so
+ that the join condition fails, then <command>MERGE</command> will
+ evaluate the condition's <literal>NOT MATCHED</literal> actions next,
+ and execute the first one that succeeds.
+ If <command>MERGE</command> attempts an <command>INSERT</command>
+ and a unique index is present and a duplicate row is concurrently
+ inserted, then a uniqueness violation is raised.
+ <command>MERGE</command> does not attempt to avoid the
+ error by executing an <command>UPDATE</command>.
+ </para>
+
<para>
Because Read Committed mode starts each command with a new snapshot
that includes all transactions committed up to that instant,
<para>
The commands <command>UPDATE</command>,
- <command>DELETE</command>, and <command>INSERT</command>
+ <command>DELETE</command>, <command>INSERT</command>, and
+ <command>MERGE</command>
acquire this lock mode on the target table (in addition to
<literal>ACCESS SHARE</literal> locks on any other referenced
tables). In general, this lock mode will be acquired by any
Another restriction on parameter symbols is that they only work in
optimizable SQL commands
(<command>SELECT</command>, <command>INSERT</command>, <command>UPDATE</command>,
- <command>DELETE</command>, and certain commands containing one of these).
+ <command>DELETE</command>, <command>MERGE</command>, and certain commands containing one of these).
In other statement
types (generically called utility statements), you must insert
values textually even if they are just data values.
</listitem>
<listitem>
<para>
- <command>UPDATE</command>, <command>INSERT</command>, and <command>DELETE</command>
+ <command>UPDATE</command>, <command>INSERT</command>, <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>
<!ENTITY listen SYSTEM "listen.sgml">
<!ENTITY load SYSTEM "load.sgml">
<!ENTITY lock SYSTEM "lock.sgml">
+<!ENTITY merge SYSTEM "merge.sgml">
<!ENTITY move SYSTEM "move.sgml">
<!ENTITY notify SYSTEM "notify.sgml">
<!ENTITY prepare SYSTEM "prepare.sgml">
</para>
<para>
- For <command>INSERT</command> and <command>UPDATE</command> statements,
+ For <command>INSERT</command>, <command>UPDATE</command>, and
+ <command>MERGE</command> statements,
<literal>WITH CHECK</literal> expressions are enforced after
<literal>BEFORE</literal> triggers are fired, and before any actual data
modifications are made. Thus a <literal>BEFORE ROW</literal> trigger may
<listitem>
<para>
Using <literal>INSERT</literal> for a policy means that it will apply
- to <literal>INSERT</literal> commands. Rows being inserted that do
+ to <literal>INSERT</literal> commands and <literal>MERGE</literal>
+ commands that contain <literal>INSERT</literal> actions.
+ Rows being inserted that do
not pass this policy will result in a policy violation error, and the
entire <literal>INSERT</literal> command will be aborted.
An <literal>INSERT</literal> policy cannot have
to <literal>UPDATE</literal>, <literal>SELECT FOR UPDATE</literal>
and <literal>SELECT FOR SHARE</literal> commands, as well as
auxiliary <literal>ON CONFLICT DO UPDATE</literal> clauses of
- <literal>INSERT</literal> commands. Since <literal>UPDATE</literal>
+ <literal>INSERT</literal> commands.
+ <literal>MERGE</literal> commands containing <literal>UPDATE</literal>
+ actions are affected as well. Since <literal>UPDATE</literal>
involves pulling an existing record and replacing it with a new
modified record, <literal>UPDATE</literal>
policies accept both a <literal>USING</literal> expression and
<entry>—</entry>
</row>
<row>
- <entry><command>INSERT</command></entry>
+ <entry><command>INSERT</command> / <command>MERGE ... THEN INSERT</command></entry>
<entry>—</entry>
<entry>New row</entry>
<entry>—</entry>
<entry>—</entry>
</row>
<row>
- <entry><command>UPDATE</command></entry>
+ <entry><command>UPDATE</command> / <command>MERGE ... THEN UPDATE</command></entry>
<entry>
Existing & new rows <footnoteref linkend="rls-select-priv"/>
</entry>
(see <link linkend="sql-createview"><command>CREATE VIEW</command></link>).
</para>
+ <para>
+ No separate policy exists for <command>MERGE</command>. Instead, the policies
+ defined for <command>SELECT</command>, <command>INSERT</command>,
+ <command>UPDATE</command>, and <command>DELETE</command> are applied
+ while executing <command>MERGE</command>, depending on the actions that are
+ performed.
+ </para>
+
<para>
Additional discussion and practical examples can be found
in <xref linkend="ddl-rowsecurity"/>.
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 mixing <command>INSERT</command>, <command>UPDATE</command>, and
+ <command>DELETE</command> within a single statement.
+ See <xref linkend="sql-merge"/>.
+ </para>
</refsect1>
<refsect1>
Also, the case in
which a column name list is omitted, but not all the columns are
filled from the <literal>VALUES</literal> clause or <replaceable>query</replaceable>,
- is disallowed by the standard.
+ is disallowed by the standard. If you prefer a more SQL standard
+ conforming statement than <literal>ON CONFLICT</literal>, see
+ <xref linkend="sql-merge"/>.
</para>
<para>
--- /dev/null
+<!--
+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>conditionally insert, update, or delete rows of a table</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> [...]
+
+<phrase>where <replaceable class="parameter">data_source</replaceable> is</phrase>
+
+{ <replaceable class="parameter">source_table_name</replaceable> | ( <replaceable class="parameter">source_query</replaceable> ) } [ [ AS ] <replaceable class="parameter">source_alias</replaceable> ]
+
+<phrase>and <replaceable class="parameter">when_clause</replaceable> is</phrase>
+
+{ WHEN MATCHED [ AND <replaceable class="parameter">condition</replaceable> ] THEN { <replaceable class="parameter">merge_update</replaceable> | <replaceable class="parameter">merge_delete</replaceable> | DO NOTHING } |
+ WHEN NOT MATCHED [ AND <replaceable class="parameter">condition</replaceable> ] THEN { <replaceable class="parameter">merge_insert</replaceable> | DO NOTHING } }
+
+<phrase>and <replaceable class="parameter">merge_insert</replaceable> is</phrase>
+
+INSERT [( <replaceable class="parameter">column_name</replaceable> [, ...] )]
+[ OVERRIDING { SYSTEM | USER } VALUE ]
+{ VALUES ( { <replaceable class="parameter">expression</replaceable> | DEFAULT } [, ...] ) | DEFAULT VALUES }
+
+<phrase>and <replaceable class="parameter">merge_update</replaceable> is</phrase>
+
+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 } [, ...] ) } [, ...]
+
+<phrase>and <replaceable class="parameter">merge_delete</replaceable> is</phrase>
+
+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. For each candidate change row, the first clause to
+ evaluate as true is executed. No more than one <literal>WHEN</literal>
+ clause is executed 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 table name 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> is specified, the source row is
+ skipped. Since actions are evaluated in their specified order, <literal>DO
+ NOTHING</literal> can be handy to skip non-interesting source rows before
+ more fine-grained handling.
+ </para>
+
+ <para>
+ There is no separate <literal>MERGE</literal> privilege.
+ If you specify an update action, you must have the
+ <literal>UPDATE</literal> privilege on the column(s)
+ of the <replaceable class="parameter">target_table_name</replaceable>
+ that are referred to in the <literal>SET</literal> clause.
+ If you specify an insert action, you must have the <literal>INSERT</literal>
+ privilege on the <replaceable class="parameter">target_table_name</replaceable>.
+ If you specify an delete action, you must have the <literal>DELETE</literal>
+ privilege 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 executed.
+ 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>
+ <command>MERGE</command> is not supported if the
+ <replaceable class="parameter">target_table_name</replaceable> is a
+ materialized view, foreign table, or if it has any
+ rules defined on it.
+ </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 INTO 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 the actual name of the table or the fact
+ that a query was issued.
+ </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 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 executed if the
+ <replaceable class="parameter">condition</replaceable> is
+ absent or it evaluates to <literal>true</literal>.
+ </para>
+ <para>
+ Conversely, 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 executed if the
+ <replaceable class="parameter">condition</replaceable> is
+ absent or it 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 for a <literal>WHEN</literal> clause
+ returns <literal>true</literal>, then the action for that clause
+ is executed for that row.
+ </para>
+ <para>
+ A condition on a <literal>WHEN MATCHED</literal> clause can refer to columns
+ in both the source and the target relations. 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 any input row 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>
+ Neither a table name nor a <literal>WHERE</literal> clause are allowed.
+ </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 table name or any other clauses, as you would normally
+ do with a <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.)
+ 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. If used in a
+ <literal>WHEN MATCHED</literal> clause, the expression can use values
+ from the original row in the target table, and values from the
+ <literal>data_source</literal> row.
+ If used in a <literal>WHEN NOT MATCHED</literal> clause, the
+ expression can use values from the <literal>data_source</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DEFAULT</literal></term>
+ <listitem>
+ <para>
+ Set the column to its default value (which will be <literal>NULL</literal>
+ 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>Notes</title>
+
+ <para>
+ The following steps take place during the execution of
+ <command>MERGE</command>.
+ <orderedlist>
+ <listitem>
+ <para>
+ Perform any <literal>BEFORE STATEMENT</literal> triggers for all
+ actions specified, whether or not their <literal>WHEN</literal>
+ clauses match.
+ </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 rows. For each candidate change row,
+ <orderedlist>
+ <listitem>
+ <para>
+ Evaluate whether each row is <literal>MATCHED</literal> or
+ <literal>NOT MATCHED</literal>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Test each <literal>WHEN</literal> condition in the order
+ specified until one returns true.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ When a condition returns true, perform the following actions:
+ <orderedlist>
+ <listitem>
+ <para>
+ Perform any <literal>BEFORE ROW</literal> triggers that fire
+ for the action's event type.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform the specified action, invoking any check constraints on the
+ target table.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform any <literal>AFTER ROW</literal> triggers that fire for
+ the action's event type.
+ </para>
+ </listitem>
+ </orderedlist>
+ </para>
+ </listitem>
+ </orderedlist>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Perform any <literal>AFTER STATEMENT</literal> 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,
+ <command>INSERT</command>) will be fired whenever we
+ <emphasis>specify</emphasis> an action of that kind.
+ In contrast, row-level triggers will fire only for the specific event type
+ being <emphasis>executed</emphasis>.
+ So a <command>MERGE</command> 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 the
+ row will cause an error.
+ This can also occur if row triggers make changes to the target table
+ and the rows so modified are then subsequently also 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 the same row are simply ignored.
+ </para>
+
+ <para>
+ If a <literal>WHEN</literal> clause omits an <literal>AND</literal>
+ sub-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 no final reachable clause is specified of either kind, it is
+ possible that no action will be taken for a candidate change row.
+ </para>
+
+ <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>
+
+ <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>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ Perform maintenance on <literal>CustomerAccounts</literal> based
+ upon new <literal>Transactions</literal>.
+
+<programlisting>
+MERGE INTO 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>
+ </para>
+
+ <para>
+ Notice that this would be exactly equivalent to the following
+ statement because the <literal>MATCHED</literal> result does not change
+ during execution.
+
+<programlisting>
+MERGE INTO 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 <literal>wine_stock_changes</literal> 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 <literal>DO NOTHING</literal> action are extensions to
+ the <acronym>SQL</acronym> standard.
+ </para>
+ </refsect1>
+</refentry>
&listen;
&load;
&lock;
+ &merge;
&move;
¬ify;
&prepare;
will be fired.
</para>
+ <para>
+ No separate triggers are defined for <command>MERGE</command>. Instead,
+ statement-level or row-level <command>UPDATE</command>,
+ <command>DELETE</command>, and <command>INSERT</command> triggers are fired
+ depending on (for statement-level triggers) what actions are specified in
+ the <command>MERGE</command> query and (for row-level triggers) what
+ actions are performed.
+ </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 or not the action is ultimately performed.
+ This is the 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 types 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
F311 Schema definition statement 03 CREATE VIEW YES
F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION YES
F311 Schema definition statement 05 GRANT statement YES
-F312 MERGE statement NO consider INSERT ... ON CONFLICT DO UPDATE
-F313 Enhanced MERGE statement NO
-F314 MERGE statement with DELETE branch NO
+F312 MERGE statement YES
+F313 Enhanced MERGE statement YES
+F314 MERGE statement with DELETE branch YES
F321 User authorization YES
F341 Usage tables YES
F361 Subprogram support YES
case CMD_DELETE:
pname = operation = "Delete";
break;
+ case CMD_MERGE:
+ pname = operation = "Merge";
+ break;
default:
pname = "???";
break;
operation = "Delete";
foperation = "Foreign Delete";
break;
+ case CMD_MERGE:
+ operation = "Merge";
+ /* XXX unsupported for now, but avoid compiler noise */
+ foperation = "Foreign Merge";
+ break;
default:
operation = "???";
foperation = "Foreign ???";
other_path, 0, es);
}
}
+ else if (node->operation == CMD_MERGE)
+ {
+ /* EXPLAIN ANALYZE display of tuples processed */
+ if (es->analyze && mtstate->ps.instrument)
+ {
+ double total;
+ double insert_path;
+ double update_path;
+ double delete_path;
+ double skipped_path;
+
+ InstrEndLoop(outerPlanState(mtstate)->instrument);
+
+ /* count the number of source rows */
+ total = outerPlanState(mtstate)->instrument->ntuples;
+ insert_path = mtstate->mt_merge_inserted;
+ update_path = mtstate->mt_merge_updated;
+ delete_path = mtstate->mt_merge_deleted;
+ skipped_path = total - insert_path - update_path - delete_path;
+ Assert(skipped_path >= 0);
+
+ 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);
ItemPointer tid,
LockTupleMode lockmode,
TupleTableSlot *oldslot,
- TupleTableSlot **newSlot);
+ TupleTableSlot **newSlot,
+ TM_FailureData *tmfpd);
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
Trigger *trigger, TriggerEvent event,
Bitmapset *modifiedCols,
TupleTableSlot *epqslot_candidate = NULL;
if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
- LockTupleExclusive, slot, &epqslot_candidate))
+ LockTupleExclusive, slot, &epqslot_candidate,
+ NULL))
return false;
/*
}
trigtuple = ExecFetchSlotHeapTuple(slot, true, &should_free);
-
}
else
{
tupleid,
LockTupleExclusive,
slot,
+ NULL,
NULL);
else
ExecForceStoreHeapTuple(fdw_trigtuple, slot, false);
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
- TupleTableSlot *newslot)
+ TupleTableSlot *newslot,
+ TM_FailureData *tmfd)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo);
/* get a copy of the on-disk tuple we are planning to update */
if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
- lockmode, oldslot, &epqslot_candidate))
+ lockmode, oldslot, &epqslot_candidate,
+ tmfd))
return false; /* cancel the update action */
/*
tupleid,
LockTupleExclusive,
oldslot,
+ NULL,
NULL);
else if (fdw_trigtuple != NULL)
ExecForceStoreHeapTuple(fdw_trigtuple, oldslot, false);
ItemPointer tid,
LockTupleMode lockmode,
TupleTableSlot *oldslot,
- TupleTableSlot **epqslot)
+ TupleTableSlot **epqslot,
+ TM_FailureData *tmfdp)
{
Relation relation = relinfo->ri_RelationDesc;
lockflags,
&tmfd);
+ /* Let the caller know about the status of this operation */
+ if (tmfdp)
+ *tmfdp = tmfd;
+
switch (test)
{
case TM_SelfModified:
bool before_trig_done; /* did we already queue BS triggers? */
bool after_trig_done; /* did we already queue AS triggers? */
AfterTriggerEventList after_trig_events; /* if so, saved list pointer */
- Tuplestorestate *old_tuplestore; /* "old" transition table, if any */
- Tuplestorestate *new_tuplestore; /* "new" transition table, if any */
+
+ /*
+ * We maintain separate transition 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 for INSERT, if any */
+ Tuplestorestate *new_ins_tuplestore;
+
TupleTableSlot *storeslot; /* for converting to tuplestore's format */
};
{
if (LocTriggerData.tg_trigger->tgoldtable)
{
- LocTriggerData.tg_oldtable = evtshared->ats_table->old_tuplestore;
+ if (TRIGGER_FIRED_BY_UPDATE(evtshared->ats_event))
+ LocTriggerData.tg_oldtable = evtshared->ats_table->old_upd_tuplestore;
+ else
+ LocTriggerData.tg_oldtable = evtshared->ats_table->old_del_tuplestore;
evtshared->ats_table->closed = true;
}
if (LocTriggerData.tg_trigger->tgnewtable)
{
- LocTriggerData.tg_newtable = evtshared->ats_table->new_tuplestore;
+ if (TRIGGER_FIRED_BY_INSERT(evtshared->ats_event))
+ LocTriggerData.tg_newtable = evtshared->ats_table->new_ins_tuplestore;
+ else
+ LocTriggerData.tg_newtable = evtshared->ats_table->new_upd_tuplestore;
evtshared->ats_table->closed = true;
}
}
MakeTransitionCaptureState(TriggerDesc *trigdesc, Oid relid, CmdType cmdType)
{
TransitionCaptureState *state;
- bool need_old,
- need_new;
+ bool need_old_upd,
+ need_new_upd,
+ need_old_del,
+ need_new_ins;
AfterTriggersTableData *table;
MemoryContext oldcxt;
ResourceOwner saveResourceOwner;
switch (cmdType)
{
case CMD_INSERT:
- need_old = false;
- need_new = trigdesc->trig_insert_new_table;
+ need_old_upd = need_old_del = need_new_upd = false;
+ need_new_ins = trigdesc->trig_insert_new_table;
break;
case CMD_UPDATE:
- need_old = trigdesc->trig_update_old_table;
- need_new = trigdesc->trig_update_new_table;
+ need_old_upd = trigdesc->trig_update_old_table;
+ need_new_upd = trigdesc->trig_update_new_table;
+ need_old_del = need_new_ins = false;
break;
case CMD_DELETE:
- need_old = trigdesc->trig_delete_old_table;
- need_new = false;
+ need_old_del = trigdesc->trig_delete_old_table;
+ need_old_upd = need_new_upd = need_new_ins = false;
+ break;
+ case CMD_MERGE:
+ need_old_upd = trigdesc->trig_update_old_table;
+ need_new_upd = trigdesc->trig_update_new_table;
+ need_old_del = trigdesc->trig_delete_old_table;
+ need_new_ins = trigdesc->trig_insert_new_table;
break;
default:
elog(ERROR, "unexpected CmdType: %d", (int) cmdType);
- need_old = need_new = false; /* keep compiler quiet */
+ /* keep compiler quiet */
+ need_old_upd = need_new_upd = need_old_del = need_new_ins = false;
break;
}
- if (!need_old && !need_new)
+ if (!need_old_upd && !need_new_upd && !need_new_ins && !need_old_del)
return NULL;
/* Check state, like AfterTriggerSaveEvent. */
saveResourceOwner = CurrentResourceOwner;
CurrentResourceOwner = CurTransactionResourceOwner;
- if (need_old && table->old_tuplestore == NULL)
- table->old_tuplestore = tuplestore_begin_heap(false, false, work_mem);
- if (need_new && table->new_tuplestore == NULL)
- table->new_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+ if (need_old_upd && table->old_upd_tuplestore == NULL)
+ table->old_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+ if (need_new_upd && table->new_upd_tuplestore == NULL)
+ table->new_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+ if (need_old_del && table->old_del_tuplestore == NULL)
+ table->old_del_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+ if (need_new_ins && table->new_ins_tuplestore == NULL)
+ table->new_ins_tuplestore = tuplestore_begin_heap(false, false, work_mem);
CurrentResourceOwner = saveResourceOwner;
MemoryContextSwitchTo(oldcxt);
{
AfterTriggersTableData *table = (AfterTriggersTableData *) lfirst(lc);
- ts = table->old_tuplestore;
- table->old_tuplestore = NULL;
+ ts = table->old_upd_tuplestore;
+ table->old_upd_tuplestore = NULL;
+ if (ts)
+ tuplestore_end(ts);
+ ts = table->new_upd_tuplestore;
+ table->new_upd_tuplestore = NULL;
+ if (ts)
+ tuplestore_end(ts);
+ ts = table->old_del_tuplestore;
+ table->old_del_tuplestore = NULL;
if (ts)
tuplestore_end(ts);
- ts = table->new_tuplestore;
- table->new_tuplestore = NULL;
+ ts = table->new_ins_tuplestore;
+ table->new_ins_tuplestore = NULL;
if (ts)
tuplestore_end(ts);
if (table->storeslot)
{
Assert(TupIsNull(newslot));
if (event == TRIGGER_EVENT_DELETE && delete_old_table)
- tuplestore = transition_capture->tcs_private->old_tuplestore;
+ tuplestore = transition_capture->tcs_private->old_del_tuplestore;
else if (event == TRIGGER_EVENT_UPDATE && update_old_table)
- tuplestore = transition_capture->tcs_private->old_tuplestore;
+ tuplestore = transition_capture->tcs_private->old_upd_tuplestore;
}
else if (!TupIsNull(newslot))
{
Assert(TupIsNull(oldslot));
if (event == TRIGGER_EVENT_INSERT && insert_new_table)
- tuplestore = transition_capture->tcs_private->new_tuplestore;
+ tuplestore = transition_capture->tcs_private->new_ins_tuplestore;
else if (event == TRIGGER_EVENT_UPDATE && update_new_table)
- tuplestore = transition_capture->tcs_private->new_tuplestore;
+ tuplestore = transition_capture->tcs_private->new_upd_tuplestore;
}
return tuplestore;
*/
if (row_trigger && transition_capture != NULL)
{
+ TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
/*
* Capture the old tuple in the appropriate transition table based on
newslot,
transition_capture);
TransitionTableAddTuple(estate, transition_capture, relinfo,
- newslot,
- transition_capture->tcs_original_insert_tuple,
- new_tuplestore);
+ newslot, original_insert_tuple, new_tuplestore);
}
/*
* If transition tables are the only reason we're here, return. As
* mentioned above, we can also be here during update tuple routing in
* presence of transition tables, in which case this function is
- * called separately for oldtup and newtup, so we expect exactly one
- * of them to be NULL.
+ * called separately for OLD and NEW, so we expect exactly one of them
+ * to be NULL.
*/
if (trigdesc == NULL ||
(event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) ||
heap table, the row-identity junk column is a CTID, but other things may
be used for other table types.) For DELETE, the plan tree need only deliver
junk row-identity column(s), and the ModifyTable node visits each of those
-rows and marks the row deleted.
+rows and marks the row deleted. MERGE is described below.
XXX a great deal more documentation needs to be written here...
are simple Vars using only one step instead of two.
+MERGE
+-----
+
+MERGE is a multiple-table, multiple-action command: It specifies a target
+table and a source relation, and can contain multiple WHEN MATCHED and
+WHEN NOT MATCHED clauses, each of which specifies one UPDATE, INSERT,
+UPDATE, or DO NOTHING actions. The target table is modified by MERGE,
+and the source relation supplies additional data for the actions. Each action
+optionally specifies a qualifying expression that is evaluated for each tuple.
+
+In the planner, transform_MERGE_to_join constructs a join between the target
+table and the source relation, with row-identifying junk columns from the target
+table. This join is an outer join if the MERGE command contains any WHEN NOT
+MATCHED clauses; the ModifyTable node fetches tuples from the plan tree of that
+join. If the row-identifying columns in the fetched tuple are NULL, then the
+source relation contains a tuple that is not matched by any tuples in the
+target table, so the qualifying expression for each WHEN NOT MATCHED clause is
+evaluated given that tuple as returned by the plan. If the expression returns
+true, the action indicated by the clause is executed, and no further clauses
+are evaluated. On the other hand, if the row-identifying columns are not
+NULL, then the matching tuple from the target table can be fetched; qualifying
+expression of each WHEN MATCHED clause is evaluated given both the fetched
+tuple and the tuple returned by the plan.
+
+If no WHEN NOT MATCHED clauses are present, then the join constructed by
+the planner is an inner join, and the row-identifying junk columns are
+always non NULL.
+
+If WHEN MATCHED ends up processing a row that is concurrently updated or deleted,
+EvalPlanQual (see below) is used to find the latest version of the row, and
+that is re-fetched; if it exists, the search for a matching WHEN MATCHED clause
+to use starts at the top.
+
+MERGE does not allow its own type of triggers, but instead fires UPDATE, DELETE,
+and INSERT triggers: row triggers are fired for each row when an action is
+executed for that row. Statement triggers are fired always, regardless of
+whether any rows match the corresponding clauses.
+
+
Memory Management
-----------------
case CMD_INSERT:
case CMD_DELETE:
case CMD_UPDATE:
+ case CMD_MERGE:
estate->es_output_cid = GetCurrentCommandId(true);
break;
resultRelInfo->ri_ReturningSlot = NULL;
resultRelInfo->ri_TrigOldSlot = NULL;
resultRelInfo->ri_TrigNewSlot = NULL;
+ resultRelInfo->ri_matchedMergeAction = NIL;
+ resultRelInfo->ri_notMatchedMergeAction = NIL;
/*
* Only ExecInitPartitionInfo() and ExecInitPartitionDispatchInfo() pass
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,
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
bool *isnull,
int maxfieldlen);
static List *adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri);
+static List *adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap);
static void ExecInitPruningContext(PartitionPruneContext *context,
List *pruning_steps,
PartitionDesc partdesc,
lappend(estate->es_tuple_routing_result_relations,
leaf_part_rri);
+ /*
+ * Initialize information about this partition that's needed to handle
+ * MERGE. We take the "first" result relation's mergeActionList as
+ * reference and make copy for this relation, converting stuff that
+ * references attribute numbers to match this relation's.
+ *
+ * This duplicates much of the logic in ExecInitMerge(), so something
+ * changes there, look here too.
+ */
+ if (node && node->operation == CMD_MERGE)
+ {
+ List *firstMergeActionList = linitial(node->mergeActionLists);
+ ListCell *lc;
+ ExprContext *econtext = mtstate->ps.ps_ExprContext;
+
+ if (part_attmap == NULL)
+ part_attmap =
+ build_attrmap_by_name(RelationGetDescr(partrel),
+ RelationGetDescr(firstResultRel));
+
+ if (unlikely(!leaf_part_rri->ri_projectNewInfoValid))
+ ExecInitMergeTupleSlots(mtstate, leaf_part_rri);
+
+ foreach(lc, firstMergeActionList)
+ {
+ /* Make a copy for this relation to be safe. */
+ MergeAction *action = copyObject(lfirst(lc));
+ MergeActionState *action_state;
+ List **list;
+
+ /* Generate the action's state for this relation */
+ action_state = makeNode(MergeActionState);
+ action_state->mas_action = action;
+
+ /* And put the action in the appropriate list */
+ if (action->matched)
+ list = &leaf_part_rri->ri_matchedMergeAction;
+ else
+ list = &leaf_part_rri->ri_notMatchedMergeAction;
+ *list = lappend(*list, action_state);
+
+ switch (action->commandType)
+ {
+ case CMD_INSERT:
+
+ /*
+ * ExecCheckPlanOutput() already done on the targetlist
+ * when "first" result relation initialized and it is same
+ * for all result relations.
+ */
+ action_state->mas_proj =
+ ExecBuildProjectionInfo(action->targetList, econtext,
+ leaf_part_rri->ri_newTupleSlot,
+ &mtstate->ps,
+ RelationGetDescr(partrel));
+ break;
+ case CMD_UPDATE:
+
+ /*
+ * Convert updateColnos from "first" result relation
+ * attribute numbers to this result rel's.
+ */
+ if (part_attmap)
+ action->updateColnos =
+ adjust_partition_colnos_using_map(action->updateColnos,
+ part_attmap);
+ action_state->mas_proj =
+ ExecBuildUpdateProjection(action->targetList,
+ true,
+ action->updateColnos,
+ RelationGetDescr(leaf_part_rri->ri_RelationDesc),
+ econtext,
+ leaf_part_rri->ri_newTupleSlot,
+ NULL);
+ break;
+ case CMD_DELETE:
+ break;
+
+ default:
+ elog(ERROR, "unknown action in MERGE WHEN clause");
+ }
+
+ /* found_whole_row intentionally ignored. */
+ action->qual =
+ map_variable_attnos(action->qual,
+ firstVarno, 0,
+ part_attmap,
+ RelationGetForm(partrel)->reltype,
+ &found_whole_row);
+ action_state->mas_whenqual =
+ ExecInitQual((List *) action->qual, &mtstate->ps);
+ }
+ }
MemoryContextSwitchTo(oldcxt);
return leaf_part_rri;
static List *
adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri)
{
- List *new_colnos = NIL;
TupleConversionMap *map = ExecGetChildToRootMap(leaf_part_rri);
- AttrMap *attrMap;
+
+ return adjust_partition_colnos_using_map(colnos, map->attrMap);
+}
+
+/*
+ * adjust_partition_colnos_using_map
+ * Like adjust_partition_colnos, but uses a caller-supplied map instead
+ * of assuming to map from the "root" result relation.
+ */
+static List *
+adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap)
+{
+ List *new_colnos = NIL;
ListCell *lc;
- Assert(map != NULL); /* else we shouldn't be here */
- attrMap = map->attrMap;
+ Assert(attrMap != NULL); /* else we shouldn't be here */
foreach(lc, colnos)
{
resultRelInfo->ri_TrigDesc->trig_update_before_row)
{
if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
- tid, NULL, slot))
+ tid, NULL, slot, NULL))
skip_tuple = true; /* "do nothing" */
}
* values plus row-locating info for UPDATE and MERGE cases, or just the
* row-locating info for DELETE cases.
*
+ * MERGE runs a join between the source relation and the target
+ * table; if any WHEN NOT MATCHED clauses are present, then the
+ * join is an outer join. In this case, any unmatched tuples will
+ * have NULL row-locating info, and only INSERT can be run. But for
+ * matched tuples, then row-locating info is used to determine the
+ * tuple to UPDATE or DELETE. When all clauses are WHEN MATCHED,
+ * then an inner join is used, so all tuples contain row-locating info.
+ *
* If the query specifies RETURNING, then the ModifyTable returns a
* RETURNING tuple after completing each row insert, update, or delete.
* It must be called again to continue the operation. Without RETURNING,
* we just loop within the node until all the work is done, then
- * return NULL. This avoids useless call/return overhead.
+ * return NULL. This avoids useless call/return overhead. (MERGE does
+ * not support RETURNING.)
*/
#include "postgres.h"
*/
TupleTableSlot *planSlot;
+ /*
+ * During EvalPlanQual, project and return the new version of the new
+ * tuple
+ */
+ TupleTableSlot *(*GetUpdateNewTuple) (ResultRelInfo *resultRelInfo,
+ TupleTableSlot *epqslot,
+ TupleTableSlot *oldSlot,
+ MergeActionState *relaction);
+
+ /* MERGE specific */
+ MergeActionState *relaction; /* MERGE action in progress */
/*
* Information about the changes that were made concurrently to a tuple
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot,
ResultRelInfo **partRelInfo);
+static TupleTableSlot *internalGetUpdateNewTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot,
+ TupleTableSlot *oldSlot,
+ MergeActionState *relaction);
+
+static TupleTableSlot *ExecMerge(ModifyTableContext *context,
+ ResultRelInfo *resultRelInfo,
+ ItemPointer tupleid,
+ bool canSetTag);
+static void ExecInitMerge(ModifyTableState *mtstate, EState *estate);
+static bool ExecMergeMatched(ModifyTableContext *context,
+ ResultRelInfo *resultRelInfo,
+ ItemPointer tupleid,
+ bool canSetTag);
+static void ExecMergeNotMatched(ModifyTableContext *context,
+ ResultRelInfo *resultRelInfo,
+ bool canSetTag);
+static TupleTableSlot *mergeGetUpdateNewTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot,
+ TupleTableSlot *oldSlot,
+ MergeActionState *relaction);
+
/*
* Verify that the tuples to be produced by INSERT match the
TupleTableSlot *planSlot,
TupleTableSlot *oldSlot)
{
- ProjectionInfo *newProj = relinfo->ri_projectNew;
- ExprContext *econtext;
-
/* Use a few extra Asserts to protect against outside callers */
Assert(relinfo->ri_projectNewInfoValid);
Assert(planSlot != NULL && !TTS_EMPTY(planSlot));
Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot));
+ return internalGetUpdateNewTuple(relinfo, planSlot, oldSlot, NULL);
+}
+
+/*
+ * Callback for ModifyTableState->GetUpdateNewTuple for use by regular UPDATE.
+ */
+static TupleTableSlot *
+internalGetUpdateNewTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot,
+ TupleTableSlot *oldSlot,
+ MergeActionState *relaction)
+{
+ ProjectionInfo *newProj = relinfo->ri_projectNew;
+ ExprContext *econtext;
+
econtext = newProj->pi_exprContext;
econtext->ecxt_outertuple = planSlot;
econtext->ecxt_scantuple = oldSlot;
return ExecProject(newProj);
}
-
/* ----------------------------------------------------------------
* ExecInsert
*
* partition, we should instead check UPDATE policies, because we are
* executing policies defined on the target table, and not those
* defined on the child partitions.
+ *
+ * If we're running MERGE, we refer to the action that we're executing
+ * to know if we're doing an INSERT or UPDATE to a partition table.
*/
- wco_kind = (mtstate->operation == CMD_UPDATE) ?
- WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
+ if (mtstate->operation == CMD_UPDATE)
+ wco_kind = WCO_RLS_UPDATE_CHECK;
+ else if (mtstate->operation == CMD_MERGE)
+ wco_kind = (context->relaction->mas_action->commandType == CMD_UPDATE) ?
+ WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
+ else
+ wco_kind = WCO_RLS_INSERT_CHECK;
/*
* ExecWithCheckOptions() will skip any WCOs which are not of the kind
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to concurrent delete")));
- /* tuple already deleted; nothing to do */
+
+ /*
+ * tuple already deleted; nothing to do. But MERGE might want
+ * to handle it differently. We've already filled-in
+ * actionInfo with sufficient information for MERGE to look
+ * at.
+ */
return NULL;
default:
elog(ERROR, "failed to fetch tuple being updated");
/* and project the new tuple to retry the UPDATE with */
context->cpUpdateRetrySlot =
- ExecGetUpdateNewTuple(resultRelInfo, epqslot, oldSlot);
+ context->GetUpdateNewTuple(resultRelInfo, epqslot, oldSlot,
+ context->relaction);
return false;
}
}
if (resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_update_before_row)
return ExecBRUpdateTriggers(context->estate, context->epqstate,
- resultRelInfo, tupleid, oldtuple, slot);
+ resultRelInfo, tupleid, oldtuple, slot,
+ &context->tmfd);
return true;
}
return TM_Ok;
}
+ /*
+ * No luck, a retry is needed. If running MERGE, we do not do so
+ * here; instead let it handle that on its own rules.
+ */
+ if (context->relaction != NULL)
+ return TM_Updated;
+
/*
* ExecCrossPartitionUpdate installed an updated version of the new
* tuple in the retry slot; start over.
/*
* If ExecUpdateAct reports that a cross-partition update was done,
- * then the returning tuple has been projected and there's nothing
- * else for us to do.
+ * then the RETURNING tuple (if any) has been projected and there's
+ * nothing else for us to do.
*/
if (updateCxt.crossPartUpdate)
return context->cpUpdateReturningSlot;
* to break.
*
* It is the user's responsibility to prevent this situation from
- * occurring. These problems are why SQL-2003 similarly specifies
- * that for SQL MERGE, an exception must be raised in the event of
- * an attempt to update the same row twice.
+ * occurring. These problems are why the 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.
*/
xminDatum = slot_getsysattr(existing,
MinTransactionIdAttributeNumber,
if (TransactionIdIsCurrentTransactionId(xmin))
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
- errmsg("ON CONFLICT DO UPDATE command cannot affect row a second time"),
+ /* translator: %s is a SQL command name */
+ errmsg("%s command cannot affect row a second time",
+ "ON CONFLICT DO UPDATE"),
errhint("Ensure that no rows proposed for insertion within the same command have duplicate constrained values.")));
/* This shouldn't happen */
return true;
}
+/*
+ * Perform MERGE.
+ */
+static TupleTableSlot *
+ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
+ ItemPointer tupleid, bool canSetTag)
+{
+ bool matched;
+
+ /*-----
+ * If we are dealing with a WHEN MATCHED case (tupleid is valid), we
+ * execute the first action for which the additional WHEN MATCHED AND
+ * 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
+ *
+ * In this case, we are still dealing with a WHEN MATCHED case.
+ * We recheck the list of WHEN MATCHED actions from the start 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 this case, the source tuple no longer matches the target tuple,
+ * so we now instead find a qualifying WHEN NOT MATCHED action to
+ * execute.
+ *
+ * XXX Hmmm, what if the updated tuple would now match one that was
+ * considered NOT MATCHED so far?
+ *
+ * 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 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.
+ */
+ matched = tupleid != NULL;
+ if (matched)
+ matched = ExecMergeMatched(context, resultRelInfo, tupleid, canSetTag);
+
+ /*
+ * Either we were dealing with a NOT MATCHED tuple or ExecMergeMatched()
+ * returned "false", indicating the previously MATCHED tuple no longer
+ * matches.
+ */
+ if (!matched)
+ ExecMergeNotMatched(context, resultRelInfo, canSetTag);
+
+ /* No RETURNING support yet */
+ return NULL;
+}
+
+/*
+ * 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 quals
+ * pass, if any. If the WHEN 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 by this routine via ExecQual, 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(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
+ ItemPointer tupleid, bool canSetTag)
+{
+ ModifyTableState *mtstate = context->mtstate;
+ TupleTableSlot *newslot;
+ EState *estate = context->estate;
+ ExprContext *econtext = mtstate->ps.ps_ExprContext;
+ bool isNull;
+ EPQState *epqstate = &mtstate->mt_epqstate;
+ ListCell *l;
+
+ /*
+ * If there are no WHEN MATCHED actions, we are done.
+ */
+ if (resultRelInfo->ri_matchedMergeAction == 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.
+ */
+ econtext->ecxt_scantuple = resultRelInfo->ri_oldTupleSlot;
+ econtext->ecxt_innertuple = context->planSlot;
+ econtext->ecxt_outertuple = NULL;
+
+lmerge_matched:;
+
+ /*
+ * This routine is only invoked for matched rows, and we must have found
+ * the tupleid of the target row in that case; fetch that tuple.
+ *
+ * We use SnapshotAny for this because we might get called again after
+ * EvalPlanQual returns us a new tuple, which may not be visible to our
+ * MVCC snapshot.
+ */
+
+ if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
+ tupleid,
+ SnapshotAny,
+ resultRelInfo->ri_oldTupleSlot))
+ elog(ERROR, "failed to fetch the target tuple");
+
+ foreach(l, resultRelInfo->ri_matchedMergeAction)
+ {
+ MergeActionState *relaction = (MergeActionState *) lfirst(l);
+ CmdType commandType = relaction->mas_action->commandType;
+ List *recheckIndexes = NIL;
+ TM_Result result;
+ UpdateContext updateCxt = {0};
+
+ /*
+ * Test condition, if any.
+ *
+ * In the absence of any 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(relaction->mas_whenqual, econtext))
+ continue;
+
+ /*
+ * Check if the existing target tuple meets 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(commandType == CMD_UPDATE ?
+ WCO_RLS_MERGE_UPDATE_CHECK : WCO_RLS_MERGE_DELETE_CHECK,
+ resultRelInfo,
+ resultRelInfo->ri_oldTupleSlot,
+ context->mtstate->ps.state);
+ }
+
+ /* Perform stated action */
+ switch (commandType)
+ {
+ case CMD_UPDATE:
+
+ /*
+ * Project the output tuple, and use that to update the table.
+ * We don't need to filter out junk attributes, because the
+ * UPDATE action's targetlist doesn't have any.
+ */
+ newslot = ExecProject(relaction->mas_proj);
+
+ context->relaction = relaction;
+ context->GetUpdateNewTuple = mergeGetUpdateNewTuple;
+ context->cpUpdateRetrySlot = NULL;
+
+ if (!ExecUpdatePrologue(context, resultRelInfo,
+ tupleid, NULL, newslot))
+ {
+ result = TM_Ok;
+ break;
+ }
+ ExecUpdatePrepareSlot(resultRelInfo, newslot, context->estate);
+ result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL,
+ newslot, mtstate->canSetTag, &updateCxt);
+ if (result == TM_Ok && updateCxt.updated)
+ {
+ ExecUpdateEpilogue(context, &updateCxt, resultRelInfo,
+ tupleid, NULL, newslot, recheckIndexes);
+ mtstate->mt_merge_updated += 1;
+ }
+
+ break;
+
+ case CMD_DELETE:
+ context->relaction = relaction;
+ if (!ExecDeletePrologue(context, resultRelInfo, tupleid,
+ NULL, NULL))
+ {
+ result = TM_Ok;
+ break;
+ }
+ result = ExecDeleteAct(context, resultRelInfo, tupleid, false);
+ if (result == TM_Ok)
+ {
+ ExecDeleteEpilogue(context, resultRelInfo, tupleid, NULL,
+ false);
+ mtstate->mt_merge_deleted += 1;
+ }
+ break;
+
+ case CMD_NOTHING:
+ /* Doing nothing is always OK */
+ result = TM_Ok;
+ break;
+
+ default:
+ elog(ERROR, "unknown action in MERGE WHEN MATCHED clause");
+ }
+
+ switch (result)
+ {
+ case TM_Ok:
+ /* all good; perform final actions */
+ if (canSetTag)
+ (estate->es_processed)++;
+
+ break;
+
+ case TM_SelfModified:
+
+ /*
+ * The SQL standard disallows this for MERGE.
+ */
+ if (TransactionIdIsCurrentTransactionId(context->tmfd.xmax))
+ ereport(ERROR,
+ (errcode(ERRCODE_CARDINALITY_VIOLATION),
+ /* translator: %s is a SQL command name */
+ errmsg("%s command cannot affect row a second time",
+ "MERGE"),
+ 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 TM_Deleted:
+ if (IsolationUsesXactSnapshot())
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to concurrent delete")));
+
+ /*
+ * If the tuple was already deleted, return to let caller
+ * handle it under NOT MATCHED clauses.
+ */
+ return false;
+
+ case TM_Updated:
+ {
+ Relation resultRelationDesc;
+ TupleTableSlot *epqslot,
+ *inputslot;
+ LockTupleMode lockmode;
+
+ /*
+ * The target tuple was concurrently updated by some other
+ * transaction.
+ */
+
+ /*
+ * If cpUpdateRetrySlot is set, ExecCrossPartitionUpdate()
+ * must have detected that the tuple was concurrently
+ * updated, so we restart the search for an appropriate
+ * WHEN MATCHED clause to process the updated tuple.
+ *
+ * In this case, ExecDelete() would already have performed
+ * EvalPlanQual() on the latest version of the tuple,
+ * which in turn would already have been loaded into
+ * ri_oldTupleSlot, so no need to do either of those
+ * things.
+ *
+ * XXX why do we not check the WHEN NOT MATCHED list in
+ * this case?
+ */
+ if (!TupIsNull(context->cpUpdateRetrySlot))
+ goto lmerge_matched;
+
+ /*
+ * Otherwise, we 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.
+ */
+ resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ lockmode = ExecUpdateLockMode(estate, resultRelInfo);
+
+ inputslot = EvalPlanQualSlot(epqstate, resultRelationDesc,
+ resultRelInfo->ri_RangeTableIndex);
+
+ result = table_tuple_lock(resultRelationDesc, tupleid,
+ estate->es_snapshot,
+ inputslot, estate->es_output_cid,
+ lockmode, LockWaitBlock,
+ TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+ &context->tmfd);
+ switch (result)
+ {
+ case TM_Ok:
+ epqslot = EvalPlanQual(epqstate,
+ resultRelationDesc,
+ resultRelInfo->ri_RangeTableIndex,
+ inputslot);
+
+ /*
+ * If we got no tuple, or the tuple we get has a
+ * NULL ctid, go back to caller: this one is not a
+ * MATCHED tuple anymore, so they can retry with
+ * NOT MATCHED actions.
+ */
+ if (TupIsNull(epqslot))
+ return false;
+
+ (void) ExecGetJunkAttribute(epqslot,
+ resultRelInfo->ri_RowIdAttNo,
+ &isNull);
+ if (isNull)
+ return false;
+
+ /*
+ * When a tuple was updated and migrated to
+ * another partition concurrently, the current
+ * MERGE implementation can't follow. There's
+ * probably a better way to handle this case, but
+ * it'd require recognizing the relation to which
+ * the tuple moved, and setting our current
+ * resultRelInfo to that.
+ */
+ if (ItemPointerIndicatesMovedPartitions(&context->tmfd.ctid))
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("tuple to be deleted was already moved to another partition due to concurrent update")));
+
+ /*
+ * A non-NULL ctid means that we are still dealing
+ * with MATCHED case. Restart the loop so that we
+ * apply all the MATCHED rules again, to ensure
+ * that the first qualifying WHEN MATCHED action
+ * is executed.
+ *
+ * Update tupleid to that of the new tuple, for
+ * the refetch we do at the top.
+ */
+ ItemPointerCopy(&context->tmfd.ctid, tupleid);
+ goto lmerge_matched;
+
+ case TM_Deleted:
+
+ /*
+ * tuple already deleted; tell caller to run NOT
+ * MATCHED actions
+ */
+ return false;
+
+ case TM_SelfModified:
+
+ /*
+ * This can be reached when following an update
+ * chain from a tuple updated by another session,
+ * reaching a tuple that was already updated in
+ * this transaction. If previously modified by
+ * this command, ignore the redundant update,
+ * otherwise error out.
+ *
+ * See also response to TM_SelfModified in
+ * ExecUpdate().
+ */
+ if (context->tmfd.cmax != estate->es_output_cid)
+ ereport(ERROR,
+ (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
+ errmsg("tuple to be updated or deleted 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.")));
+ return false;
+
+ default:
+ /* see table_tuple_lock call in ExecDelete() */
+ elog(ERROR, "unexpected table_tuple_lock status: %u",
+ result);
+ return false;
+ }
+ }
+
+ case TM_Invisible:
+ case TM_WouldBlock:
+ case TM_BeingModified:
+ /* these should not occur */
+ elog(ERROR, "unexpected tuple operation result: %d", result);
+ break;
+ }
+
+ /*
+ * We've activated one of the WHEN clauses, so we don't search
+ * further. This is required behaviour, not an optimization.
+ */
+ break;
+ }
+
+ /*
+ * Successfully executed an action or no qualifying action was found.
+ */
+ return true;
+}
+
+/*
+ * Execute the first qualifying NOT MATCHED action.
+ */
+static void
+ExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
+ bool canSetTag)
+{
+ ModifyTableState *mtstate = context->mtstate;
+ ExprContext *econtext = mtstate->ps.ps_ExprContext;
+ List *actionStates = NIL;
+ ListCell *l;
+
+ /*
+ * For INSERT actions, the 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.
+ *
+ * XXX does this mean that we can avoid creating copies of actionStates on
+ * partitioned tables, for not-matched actions?
+ */
+ actionStates = resultRelInfo->ri_notMatchedMergeAction;
+
+ /*
+ * Make source tuple available to ExecQual and ExecProject. We don't need
+ * the target tuple, since the WHEN quals and targetlist can't refer to
+ * the target columns.
+ */
+ econtext->ecxt_scantuple = NULL;
+ econtext->ecxt_innertuple = context->planSlot;
+ econtext->ecxt_outertuple = NULL;
+
+ foreach(l, actionStates)
+ {
+ MergeActionState *action = (MergeActionState *) lfirst(l);
+ CmdType commandType = action->mas_action->commandType;
+ TupleTableSlot *newslot;
+
+ /*
+ * Test condition, if any.
+ *
+ * In the absence of any 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->mas_whenqual, econtext))
+ continue;
+
+ /* Perform stated action */
+ switch (commandType)
+ {
+ case CMD_INSERT:
+
+ /*
+ * Project the tuple. In case of a partitioned table, the
+ * projection was already built to use the root's descriptor,
+ * so we don't need to map the tuple here.
+ */
+ newslot = ExecProject(action->mas_proj);
+ context->relaction = action;
+
+ (void) ExecInsert(context, mtstate->rootResultRelInfo, newslot,
+ canSetTag, NULL, NULL);
+ mtstate->mt_merge_inserted += 1;
+ break;
+ case CMD_NOTHING:
+ /* Do nothing */
+ break;
+ default:
+ elog(ERROR, "unknown action in MERGE WHEN NOT MATCHED clause");
+ }
+
+ /*
+ * We've activated one of the WHEN clauses, so we don't search
+ * further. This is required behaviour, not an optimization.
+ */
+ break;
+ }
+}
+
+/*
+ * Initialize state for execution of MERGE.
+ */
+void
+ExecInitMerge(ModifyTableState *mtstate, EState *estate)
+{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ ResultRelInfo *rootRelInfo = mtstate->rootResultRelInfo;
+ ResultRelInfo *resultRelInfo;
+ ExprContext *econtext;
+ ListCell *lc;
+ int i;
+
+ if (node->mergeActionLists == NIL)
+ return;
+
+ mtstate->mt_merge_subcommands = 0;
+
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ econtext = mtstate->ps.ps_ExprContext;
+
+ /*
+ * Create a MergeActionState for each action on the mergeActionList and
+ * add it to either a list of matched actions or not-matched actions.
+ *
+ * Similar logic appears in ExecInitPartitionInfo(), so if changing
+ * anything here, do so there too.
+ */
+ i = 0;
+ foreach(lc, node->mergeActionLists)
+ {
+ List *mergeActionList = lfirst(lc);
+ TupleDesc relationDesc;
+ ListCell *l;
+
+ resultRelInfo = mtstate->resultRelInfo + i;
+ i++;
+ relationDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ /* initialize slots for MERGE fetches from this rel */
+ if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
+ ExecInitMergeTupleSlots(mtstate, resultRelInfo);
+
+ foreach(l, mergeActionList)
+ {
+ MergeAction *action = (MergeAction *) lfirst(l);
+ MergeActionState *action_state;
+ TupleTableSlot *tgtslot;
+ TupleDesc tgtdesc;
+ List **list;
+
+ /*
+ * Build action merge state for this rel. (For partitions,
+ * equivalent code exists in ExecInitPartitionInfo.)
+ */
+ action_state = makeNode(MergeActionState);
+ action_state->mas_action = action;
+ action_state->mas_whenqual = ExecInitQual((List *) action->qual,
+ &mtstate->ps);
+
+ /*
+ * 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->mas_action->matched)
+ list = &resultRelInfo->ri_matchedMergeAction;
+ else
+ list = &resultRelInfo->ri_notMatchedMergeAction;
+ *list = lappend(*list, action_state);
+
+ switch (action->commandType)
+ {
+ case CMD_INSERT:
+ ExecCheckPlanOutput(rootRelInfo->ri_RelationDesc,
+ action->targetList);
+
+ /*
+ * If the MERGE targets a partitioned table, any INSERT
+ * actions must be routed through it, not the child
+ * relations. Initialize the routing struct and the root
+ * table's "new" tuple slot for that, if not already done.
+ * The projection we prepare, for all relations, uses the
+ * root relation descriptor, and targets the plan's root
+ * slot. (This is consistent with the fact that we
+ * checked the plan output to match the root relation,
+ * above.)
+ */
+ if (rootRelInfo->ri_RelationDesc->rd_rel->relkind ==
+ RELKIND_PARTITIONED_TABLE)
+ {
+ if (mtstate->mt_partition_tuple_routing == NULL)
+ {
+ /*
+ * Initialize planstate for routing if not already
+ * done.
+ *
+ * Note that the slot is managed as a standalone
+ * slot belonging to ModifyTableState, so we pass
+ * NULL for the 2nd argument.
+ */
+ mtstate->mt_root_tuple_slot =
+ table_slot_create(rootRelInfo->ri_RelationDesc,
+ NULL);
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate,
+ rootRelInfo->ri_RelationDesc);
+ }
+ tgtslot = mtstate->mt_root_tuple_slot;
+ tgtdesc = RelationGetDescr(rootRelInfo->ri_RelationDesc);
+ }
+ else
+ {
+ /* not partitioned? use the stock relation and slot */
+ tgtslot = resultRelInfo->ri_newTupleSlot;
+ tgtdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+ }
+
+ action_state->mas_proj =
+ ExecBuildProjectionInfo(action->targetList, econtext,
+ tgtslot,
+ &mtstate->ps,
+ tgtdesc);
+
+ mtstate->mt_merge_subcommands |= MERGE_INSERT;
+ break;
+ case CMD_UPDATE:
+ action_state->mas_proj =
+ ExecBuildUpdateProjection(action->targetList,
+ true,
+ action->updateColnos,
+ relationDesc,
+ econtext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps);
+ 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;
+ }
+ }
+ }
+}
+
+/*
+ * Initializes the tuple slots in a ResultRelInfo for any MERGE action.
+ *
+ * We mark 'projectNewInfoValid' even though the projections themselves
+ * are not initialized here.
+ */
+void
+ExecInitMergeTupleSlots(ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo)
+{
+ EState *estate = mtstate->ps.state;
+
+ Assert(!resultRelInfo->ri_projectNewInfoValid);
+
+ resultRelInfo->ri_oldTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &estate->es_tupleTable);
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &estate->es_tupleTable);
+ resultRelInfo->ri_projectNewInfoValid = true;
+}
+
+/*
+ * Callback for ModifyTableContext->GetUpdateNewTuple for use by MERGE. It
+ * computes the updated tuple by projecting from the current merge action's
+ * projection.
+ */
+static TupleTableSlot *
+mergeGetUpdateNewTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot,
+ TupleTableSlot *oldSlot,
+ MergeActionState *relaction)
+{
+ ExprContext *econtext = relaction->mas_proj->pi_exprContext;
+
+ econtext->ecxt_scantuple = oldSlot;
+ econtext->ecxt_innertuple = planSlot;
+
+ return ExecProject(relaction->mas_proj);
+}
/*
* Process BEFORE EACH STATEMENT triggers
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;
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;
datum = ExecGetJunkAttribute(planSlot, node->mt_resultOidAttno,
&isNull);
if (isNull)
+ {
+ /*
+ * For commands other than MERGE, any tuples having InvalidOid
+ * for tableoid are errors. For MERGE, we may need to handle
+ * them as WHEN NOT MATCHED clauses if any, so do that.
+ *
+ * Note that we use the node's toplevel resultRelInfo, not any
+ * specific partition's.
+ */
+ if (operation == CMD_MERGE)
+ {
+ EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
+
+ context.planSlot = planSlot;
+ context.lockmode = 0;
+
+ ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag);
+ continue; /* no RETURNING support yet */
+ }
+
elog(ERROR, "tableoid is NULL");
+ }
resultoid = DatumGetObjectId(datum);
/* If it's not the same as last time, we need to locate the rel */
oldtuple = NULL;
/*
- * For UPDATE/DELETE, fetch the row identity info for the tuple to be
- * updated/deleted. For a heap relation, that's a TID; otherwise we
- * may have a wholerow junk attr that carries the old tuple in toto.
- * Keep this in step with the part of ExecInitModifyTable that sets up
- * ri_RowIdAttNo.
+ * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
+ * to be updated/deleted/merged. For a heap relation, that's a TID;
+ * otherwise we may have a wholerow junk attr that carries the old
+ * tuple in toto. Keep this in step with the part of
+ * ExecInitModifyTable that sets up ri_RowIdAttNo.
*/
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE ||
+ operation == CMD_MERGE)
{
char relkind;
Datum datum;
datum = ExecGetJunkAttribute(slot,
resultRelInfo->ri_RowIdAttNo,
&isNull);
- /* shouldn't ever get a null result... */
+
+ /*
+ * For commands other than MERGE, any tuples having a null row
+ * identifier are errors. For MERGE, we may need to handle
+ * them as WHEN NOT MATCHED clauses if any, so do that.
+ *
+ * Note that we use the node's toplevel resultRelInfo, not any
+ * specific partition's.
+ */
if (isNull)
+ {
+ if (operation == CMD_MERGE)
+ {
+ EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
+
+ context.planSlot = planSlot;
+ context.lockmode = 0;
+
+ ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag);
+ continue; /* no RETURNING support yet */
+ }
+
elog(ERROR, "ctid is NULL");
+ }
tupleid = (ItemPointer) DatumGetPointer(datum);
tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
oldSlot))
elog(ERROR, "failed to fetch tuple being updated");
}
- slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot,
- oldSlot);
+ slot = internalGetUpdateNewTuple(resultRelInfo, planSlot,
+ oldSlot, NULL);
+ context.GetUpdateNewTuple = internalGetUpdateNewTuple;
+ context.relaction = NULL;
/* Now apply the update. */
slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
true, false, node->canSetTag, NULL, NULL);
break;
+ case CMD_MERGE:
+ slot = ExecMerge(&context, resultRelInfo, tupleid, node->canSetTag);
+ break;
+
default:
elog(ERROR, "unknown operation");
break;
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nrels * sizeof(ResultRelInfo));
+ mtstate->mt_merge_inserted = 0;
+ mtstate->mt_merge_updated = 0;
+ mtstate->mt_merge_deleted = 0;
+
/*----------
* Resolve the target relation. This is the same as:
*
}
/*
- * For UPDATE/DELETE, find the appropriate junk attr now, either a
- * 'ctid' or 'wholerow' attribute depending on relkind. For foreign
+ * For UPDATE/DELETE/MERGE, find the appropriate junk attr now, either
+ * a 'ctid' or 'wholerow' attribute depending on relkind. For foreign
* tables, the FDW might have created additional junk attr(s), but
* those are no concern of ours.
*/
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE ||
+ operation == CMD_MERGE)
{
char relkind;
}
else if (relkind == RELKIND_FOREIGN_TABLE)
{
+ /*
+ * We don't support MERGE with foreign tables for now. (It's
+ * problematic because the implementation uses CTID.)
+ */
+ Assert(operation != CMD_MERGE);
+
/*
* When there is a row-level trigger, there should be a
* wholerow attribute. We also require it to be present in
- * UPDATE, so we can get the values of unchanged columns.
+ * UPDATE and MERGE, so we can get the values of unchanged
+ * columns.
*/
resultRelInfo->ri_RowIdAttNo =
ExecFindJunkAttributeInTlist(subplan->targetlist,
"wholerow");
- if (mtstate->operation == CMD_UPDATE &&
+ if ((mtstate->operation == CMD_UPDATE || mtstate->operation == CMD_MERGE) &&
!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
elog(ERROR, "could not find junk wholerow column");
}
else
{
+ /* No support for MERGE */
+ Assert(operation != CMD_MERGE);
/* Other valid target relkinds must provide wholerow */
resultRelInfo->ri_RowIdAttNo =
ExecFindJunkAttributeInTlist(subplan->targetlist,
}
/*
- * If this is an inherited update/delete, there will be a junk attribute
- * named "tableoid" present in the subplan's targetlist. It will be used
- * to identify the result relation for a given tuple to be
- * updated/deleted.
+ * If this is an inherited update/delete/merge, there will be a junk
+ * attribute named "tableoid" present in the subplan's targetlist. It
+ * will be used to identify the result relation for a given tuple to be
+ * updated/deleted/merged.
*/
mtstate->mt_resultOidAttno =
ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
/*
* Build state for tuple routing if it's a partitioned INSERT. An UPDATE
- * might need this too, but only if it actually moves tuples between
- * partitions; in that case setup is done by ExecCrossPartitionUpdate.
+ * or MERGE might need this too, but only if it actually moves tuples
+ * between partitions; in that case setup is done by
+ * ExecCrossPartitionUpdate.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
operation == CMD_INSERT)
arowmarks = lappend(arowmarks, aerm);
}
+ /* For a MERGE command, initialize its state */
+ if (mtstate->operation == CMD_MERGE)
+ ExecInitMerge(mtstate, estate);
+
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks);
/*
else
res = SPI_OK_UPDATE;
break;
+ case CMD_MERGE:
+ res = SPI_OK_MERGE;
+ break;
default:
return SPI_ERROR_OPUNKNOWN;
}
COPY_NODE_FIELD(onConflictWhere);
COPY_SCALAR_FIELD(exclRelRTI);
COPY_NODE_FIELD(exclRelTlist);
+ COPY_NODE_FIELD(mergeActionLists);
return newnode;
}
return newnode;
}
+static MergeWhenClause *
+_copyMergeWhenClause(const MergeWhenClause *from)
+{
+ MergeWhenClause *newnode = makeNode(MergeWhenClause);
+
+ COPY_SCALAR_FIELD(matched);
+ COPY_SCALAR_FIELD(commandType);
+ COPY_SCALAR_FIELD(override);
+ COPY_NODE_FIELD(condition);
+ COPY_NODE_FIELD(targetList);
+ COPY_NODE_FIELD(values);
+ 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);
+ COPY_NODE_FIELD(updateColnos);
+
+ return newnode;
+}
+
static A_Expr *
_copyA_Expr(const A_Expr *from)
{
COPY_NODE_FIELD(setOperations);
COPY_NODE_FIELD(constraintDeps);
COPY_NODE_FIELD(withCheckOptions);
+ COPY_NODE_FIELD(mergeActionList);
+ COPY_SCALAR_FIELD(mergeUseOuterJoin);
COPY_LOCATION_FIELD(stmt_location);
COPY_SCALAR_FIELD(stmt_len);
return newnode;
}
+static MergeStmt *
+_copyMergeStmt(const MergeStmt *from)
+{
+ MergeStmt *newnode = makeNode(MergeStmt);
+
+ COPY_NODE_FIELD(relation);
+ COPY_NODE_FIELD(sourceRelation);
+ COPY_NODE_FIELD(joinCondition);
+ COPY_NODE_FIELD(mergeWhenClauses);
+ COPY_NODE_FIELD(withClause);
+
+ return newnode;
+}
+
static SelectStmt *
_copySelectStmt(const SelectStmt *from)
{
case T_UpdateStmt:
retval = _copyUpdateStmt(from);
break;
+ case T_MergeStmt:
+ retval = _copyMergeStmt(from);
+ break;
case T_SelectStmt:
retval = _copySelectStmt(from);
break;
case T_CommonTableExpr:
retval = _copyCommonTableExpr(from);
break;
+ case T_MergeWhenClause:
+ retval = _copyMergeWhenClause(from);
+ break;
+ case T_MergeAction:
+ retval = _copyMergeAction(from);
+ break;
case T_ObjectWithArgs:
retval = _copyObjectWithArgs(from);
break;
COMPARE_NODE_FIELD(setOperations);
COMPARE_NODE_FIELD(constraintDeps);
COMPARE_NODE_FIELD(withCheckOptions);
+ COMPARE_NODE_FIELD(mergeActionList);
+ COMPARE_SCALAR_FIELD(mergeUseOuterJoin);
COMPARE_LOCATION_FIELD(stmt_location);
COMPARE_SCALAR_FIELD(stmt_len);
return true;
}
+static bool
+_equalMergeStmt(const MergeStmt *a, const MergeStmt *b)
+{
+ COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(sourceRelation);
+ COMPARE_NODE_FIELD(joinCondition);
+ COMPARE_NODE_FIELD(mergeWhenClauses);
+ COMPARE_NODE_FIELD(withClause);
+
+ return true;
+}
+
static bool
_equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
{
return true;
}
+static bool
+_equalMergeWhenClause(const MergeWhenClause *a, const MergeWhenClause *b)
+{
+ COMPARE_SCALAR_FIELD(matched);
+ COMPARE_SCALAR_FIELD(commandType);
+ COMPARE_SCALAR_FIELD(override);
+ COMPARE_NODE_FIELD(condition);
+ COMPARE_NODE_FIELD(targetList);
+ COMPARE_NODE_FIELD(values);
+
+ 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);
+ COMPARE_NODE_FIELD(updateColnos);
+
+ return true;
+}
+
static bool
_equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
{
case T_UpdateStmt:
retval = _equalUpdateStmt(a, b);
break;
+ case T_MergeStmt:
+ retval = _equalMergeStmt(a, b);
+ break;
case T_SelectStmt:
retval = _equalSelectStmt(a, b);
break;
case T_CommonTableExpr:
retval = _equalCommonTableExpr(a, b);
break;
+ case T_MergeWhenClause:
+ retval = _equalMergeWhenClause(a, b);
+ break;
+ case T_MergeAction:
+ retval = _equalMergeAction(a, b);
+ break;
case T_ObjectWithArgs:
retval = _equalObjectWithArgs(a, b);
break;
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;
return true;
if (walker((Node *) query->onConflict, 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))
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;
MUTATE(query->targetList, query->targetList, List *);
MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
MUTATE(query->onConflict, query->onConflict, OnConflictExpr *);
+ MUTATE(query->mergeActionList, query->mergeActionList, List *);
MUTATE(query->returningList, query->returningList, List *);
MUTATE(query->jointree, query->jointree, FromExpr *);
MUTATE(query->setOperations, query->setOperations, Node *);
* boundaries: we descend to everything that's possibly interesting.
*
* Currently, the node type coverage here extends only to DML statements
- * (SELECT/INSERT/UPDATE/DELETE) and nodes that can appear in them, because
- * this is used mainly during analysis of CTEs, and only DML statements can
- * appear in CTEs.
+ * (SELECT/INSERT/UPDATE/DELETE/MERGE) and nodes that can appear in them,
+ * because this is used mainly during analysis of CTEs, and only DML
+ * statements can appear in CTEs.
*/
bool
raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_MergeStmt:
+ {
+ MergeStmt *stmt = (MergeStmt *) node;
+
+ if (walker(stmt->relation, context))
+ return true;
+ if (walker(stmt->sourceRelation, context))
+ return true;
+ if (walker(stmt->joinCondition, 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->values, context))
+ return true;
+ }
+ break;
case T_SelectStmt:
{
SelectStmt *stmt = (SelectStmt *) node;
WRITE_NODE_FIELD(onConflictWhere);
WRITE_UINT_FIELD(exclRelRTI);
WRITE_NODE_FIELD(exclRelTlist);
+ WRITE_NODE_FIELD(mergeActionLists);
}
static void
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(onconflict);
WRITE_INT_FIELD(epqParam);
+ WRITE_NODE_FIELD(mergeActionLists);
}
static void
WRITE_NODE_FIELD(setOperations);
WRITE_NODE_FIELD(constraintDeps);
WRITE_NODE_FIELD(withCheckOptions);
+ WRITE_NODE_FIELD(mergeActionList);
+ WRITE_BOOL_FIELD(mergeUseOuterJoin);
WRITE_LOCATION_FIELD(stmt_location);
WRITE_INT_FIELD(stmt_len);
}
WRITE_NODE_FIELD(ctecolcollations);
}
+static void
+_outMergeWhenClause(StringInfo str, const MergeWhenClause *node)
+{
+ WRITE_NODE_TYPE("MERGEWHENCLAUSE");
+
+ WRITE_BOOL_FIELD(matched);
+ WRITE_ENUM_FIELD(commandType, CmdType);
+ WRITE_ENUM_FIELD(override, OverridingKind);
+ WRITE_NODE_FIELD(condition);
+ WRITE_NODE_FIELD(targetList);
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outMergeAction(StringInfo str, const MergeAction *node)
+{
+ WRITE_NODE_TYPE("MERGEACTION");
+
+ WRITE_BOOL_FIELD(matched);
+ WRITE_ENUM_FIELD(commandType, CmdType);
+ WRITE_ENUM_FIELD(override, OverridingKind);
+ WRITE_NODE_FIELD(qual);
+ WRITE_NODE_FIELD(targetList);
+ WRITE_NODE_FIELD(updateColnos);
+}
+
static void
_outSetOperationStmt(StringInfo str, const SetOperationStmt *node)
{
case T_CommonTableExpr:
_outCommonTableExpr(str, obj);
break;
+ case T_MergeWhenClause:
+ _outMergeWhenClause(str, obj);
+ break;
+ case T_MergeAction:
+ _outMergeAction(str, obj);
+ break;
case T_SetOperationStmt:
_outSetOperationStmt(str, obj);
break;
READ_NODE_FIELD(setOperations);
READ_NODE_FIELD(constraintDeps);
READ_NODE_FIELD(withCheckOptions);
+ READ_NODE_FIELD(mergeActionList);
+ READ_BOOL_FIELD(mergeUseOuterJoin);
READ_LOCATION_FIELD(stmt_location);
READ_INT_FIELD(stmt_len);
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(values);
+ READ_ENUM_FIELD(override, OverridingKind);
+
+ READ_DONE();
+}
+
+/*
+ * _readMergeAction
+ */
+static MergeAction *
+_readMergeAction(void)
+{
+ READ_LOCALS(MergeAction);
+
+ READ_BOOL_FIELD(matched);
+ READ_ENUM_FIELD(commandType, CmdType);
+ READ_ENUM_FIELD(override, OverridingKind);
+ READ_NODE_FIELD(qual);
+ READ_NODE_FIELD(targetList);
+ READ_NODE_FIELD(updateColnos);
+
+ READ_DONE();
+}
+
/*
* _readSetOperationStmt
*/
READ_NODE_FIELD(onConflictWhere);
READ_UINT_FIELD(exclRelRTI);
READ_NODE_FIELD(exclRelTlist);
+ READ_NODE_FIELD(mergeActionLists);
READ_DONE();
}
return_value = _readCTECycleClause();
else if (MATCH("COMMONTABLEEXPR", 15))
return_value = _readCommonTableExpr();
+ else if (MATCH("MERGEWHENCLAUSE", 15))
+ return_value = _readMergeWhenClause();
+ else if (MATCH("MERGEACTION", 11))
+ return_value = _readMergeAction();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
else if (MATCH("ALIAS", 5))
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
- List *rowMarks, OnConflictExpr *onconflict, int epqParam);
+ List *rowMarks, OnConflictExpr *onconflict,
+ List *mergeActionList, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
GatherMergePath *best_path);
best_path->returningLists,
best_path->rowMarks,
best_path->onconflict,
+ best_path->mergeActionLists,
best_path->epqParam);
copy_generic_path_info(&plan->plan, &best_path->path);
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
- List *rowMarks, OnConflictExpr *onconflict, int epqParam)
+ List *rowMarks, OnConflictExpr *onconflict,
+ List *mergeActionLists, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list;
ListCell *lc;
int i;
- Assert(operation == CMD_UPDATE ?
- list_length(resultRelations) == list_length(updateColnosLists) :
- updateColnosLists == NIL);
+ Assert(operation == CMD_MERGE ||
+ (operation == CMD_UPDATE ?
+ list_length(resultRelations) == list_length(updateColnosLists) :
+ updateColnosLists == NIL));
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
+ node->mergeActionLists = mergeActionLists;
node->epqParam = epqParam;
/*
if (parse->cteList)
SS_process_ctes(root);
+ /*
+ * If it's a MERGE command, transform the joinlist as appropriate.
+ */
+ transform_MERGE_to_join(parse);
+
/*
* If the FROM clause is empty, replace it with a dummy RTE_RESULT RTE, so
* that we don't need so many special cases to deal with that situation.
/* 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);
+ }
+
root->append_rel_list = (List *)
preprocess_expression(root, (Node *) root->append_rel_list,
EXPRKIND_APPINFO);
}
/*
- * If this is an INSERT/UPDATE/DELETE, add the ModifyTable node.
+ &n