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