From 2f2b6e0204a24b013ab0c5978dd907ebca95bfff Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 6 Jul 2021 11:02:39 +0800
Subject: [PATCH] 4

---
 contrib/test_decoding/expected/ddl.out        |   4 +
 doc/src/sgml/func.sgml                        |  61 ++
 doc/src/sgml/ref/alter_foreign_table.sgml     |  13 +
 doc/src/sgml/ref/alter_table.sgml             |  12 +
 doc/src/sgml/ref/create_foreign_table.sgml    |  37 ++
 doc/src/sgml/ref/create_table.sgml            |  38 ++
 doc/src/sgml/ref/create_table_as.sgml         |  23 +
 src/test/regress/expected/alter_table.out     |   2 +
 src/test/regress/expected/compression_1.out   |   9 +
 src/test/regress/expected/copy2.out           |   1 +
 src/test/regress/expected/create_table.out    |  14 +
 .../regress/expected/create_table_like.out    |   8 +
 src/test/regress/expected/domain.out          |   2 +
 src/test/regress/expected/foreign_data.out    |  42 ++
 src/test/regress/expected/identity.out        |   1 +
 src/test/regress/expected/inherit.out         |  13 +
 src/test/regress/expected/insert.out          |  12 +
 src/test/regress/expected/insert_parallel.out | 588 ++++++++++++++++++
 src/test/regress/expected/psql.out            |  58 +-
 src/test/regress/expected/publication.out     |   4 +
 .../regress/expected/replica_identity.out     |   1 +
 src/test/regress/expected/rowsecurity.out     |   1 +
 src/test/regress/expected/rules.out           |   3 +
 src/test/regress/expected/stats_ext.out       |   1 +
 src/test/regress/expected/update.out          |   1 +
 src/test/regress/output/tablespace.source     |   2 +
 src/test/regress/parallel_schedule            |   1 +
 src/test/regress/sql/insert_parallel.sql      | 345 ++++++++++
 28 files changed, 1270 insertions(+), 27 deletions(-)
 create mode 100644 src/test/regress/expected/insert_parallel.out
 create mode 100644 src/test/regress/sql/insert_parallel.sql

diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 4ff0044c78..1d7eebb897 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -446,6 +446,7 @@ WITH (user_catalog_table = true)
  options  | text[]  |           |          |                                                  | extended |              | 
 Indexes:
     "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
 Options: user_catalog_table=true
 
 INSERT INTO replication_metadata(relation, options)
@@ -460,6 +461,7 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
  options  | text[]  |           |          |                                                  | extended |              | 
 Indexes:
     "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
 
 INSERT INTO replication_metadata(relation, options)
 VALUES ('bar', ARRAY['a', 'b']);
@@ -473,6 +475,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
  options  | text[]  |           |          |                                                  | extended |              | 
 Indexes:
     "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
 Options: user_catalog_table=true
 
 INSERT INTO replication_metadata(relation, options)
@@ -492,6 +495,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
  rewritemeornot | integer |           |          |                                                  | plain    |              | 
 Indexes:
     "replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
 Options: user_catalog_table=false
 
 INSERT INTO replication_metadata(relation, options)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6388385edc..b496332217 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23909,6 +23909,67 @@ SELECT collation for ('foo' COLLATE "de_DE");
         Undefined objects are identified with <literal>NULL</literal> values.
        </para></entry>
       </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_get_table_parallel_dml_safety</primary>
+        </indexterm>
+        <function>pg_get_table_parallel_dml_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+        <returnvalue>record</returnvalue>
+        ( <parameter>objid</parameter> <type>oid</type>,
+        <parameter>classid</parameter> <type>oid</type>,
+        <parameter>proparallel</parameter> <type>char</type> )
+       </para>
+       <para>
+        Returns a row containing enough information to uniquely identify the
+        parallel unsafe/restricted table-related objects from which the
+        table's parallel DML safety is determined. The user can use this
+        information during development in order to accurately declare a
+        table's parallel DML safety, or to identify any problematic objects
+        if parallel DML fails or behaves unexpectedly. Note that when the
+        use of an object-related parallel unsafe/restricted function is
+        detected, both the function OID and the object OID are returned.
+        <parameter>classid</parameter> is the OID of the system catalog
+        containing the object;
+        <parameter>objid</parameter> is the OID of the object itself.
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_get_table_max_parallel_dml_hazard</primary>
+        </indexterm>
+        <function>pg_get_table_max_parallel_dml_hazard</function> ( <type>regclass</type> )
+        <returnvalue>char</returnvalue>
+       </para>
+       <para>
+        Returns the worst parallel DML safety hazard that can be found in the
+        given relation:
+         <itemizedlist>
+          <listitem>
+           <para>
+            <literal>s</literal> safe
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>r</literal> restricted
+           </para>
+          </listitem>
+          <listitem>
+           <para>
+            <literal>u</literal> unsafe
+           </para>
+          </listitem>
+         </itemizedlist>
+       </para>
+       <para>
+        Users can use this function to do a quick check without caring about
+        specific parallel-related objects.
+       </para></entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 7ca03f3ac9..c1652e8312 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -29,6 +29,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
     RENAME TO <replaceable class="parameter">new_name</replaceable>
 ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
     SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+    PARALLEL { UNSAFE | RESTRICTED | SAFE }
 
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
@@ -299,6 +301,17 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PARALLEL</literal></term>
+    <listitem>
+     <para>
+      Change whether the data in the table can be modified in parallel mode.
+      See the similar form of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>
+      for more details.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c5e5e84e06..963c8cfcc6 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -37,6 +37,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
     ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
 ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
     DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+    PARALLEL { UNSAFE | RESTRICTED | SAFE }
 
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
@@ -1010,6 +1012,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PARALLEL</literal></term>
+    <listitem>
+     <para>
+      Change whether the data in the table can be modified in parallel mode.
+      See <link linkend="sql-createtable"><command>CREATE TABLE</command></link> for details.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477efe58..fbd5371eca 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
 
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
 ) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
 
@@ -290,6 +292,41 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+    <listitem>
+     <para>
+      <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+      can't be modified in parallel mode, and this forces a serial execution plan 
+      for DML statements operating on the table. This is the default.
+      <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+      table can be modified in parallel mode, but the modification is
+      restricted to the parallel group leader. 
+      <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+      can be modified in parallel mode without restriction. Note that 
+      <productname>PostgreSQL</productname> currently does not support data 
+      modification by parallel workers.
+     </para>
+
+     <para>
+      Tables should be labeled parallel dml unsafe/restricted if any parallel
+      unsafe/restricted function could be executed when modifying the data in
+      the table (e.g., functions in triggers/index expression/constraints etc.).
+     </para>
+
+     <para>
+      To assist in correctly labeling the parallel DML safety level of a table,
+      PostgreSQL provides some utility functions that may be used during
+      application development. Refer to
+      <link linkend="functions-info-object-table">
+      <function>pg_get_table_parallel_dml_safety()</function></link> and
+      <link linkend="functions-info-object-table">
+      <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">server_name</replaceable></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..d6db677e3a 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
 
 CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
     OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
 
 CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
     PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
 
 <phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
 
@@ -1336,6 +1339,41 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-createtable-paralleldmlsafety">
+    <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+    <listitem>
+     <para>
+      <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+      can't be modified in parallel mode, and this forces a serial execution plan
+      for DML statements operating on the table. This is the default.
+      <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+      table can be modified in parallel mode, but the modification is
+      restricted to the parallel group leader.
+      <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+      can be modified in parallel mode without restriction. Note that
+      <productname>PostgreSQL</productname> currently does not support data
+      modification by parallel workers.
+     </para>
+
+     <para>
+      Tables should be labeled parallel dml unsafe/restricted if any parallel
+      unsafe/restricted function could be executed when modifying the data in
+      the table
+      (e.g., functions in triggers/index expressions/constraints etc.).
+     </para>
+
+     <para>
+      To assist in correctly labeling the parallel DML safety level of a table,
+      PostgreSQL provides some utility functions that may be used during
+      application development. Refer to
+      <link linkend="functions-info-object-table">
+      <function>pg_get_table_parallel_dml_safety()</function></link> and
+      <link linkend="functions-info-object-table">
+      <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab56c..71c932b048 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
     [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
     [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+    [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
     AS <replaceable>query</replaceable>
     [ WITH [ NO ] DATA ]
 </synopsis>
@@ -223,6 +224,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+    <listitem>
+     <para>
+      <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+      can't be modified in parallel mode. This is the default.
+      <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+      table can be modified in parallel mode, but the modification is
+      restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+      indicates that the table is safe to be modified in parallel mode without
+      restriction. But note that <productname>PostgreSQL</productname>
+      does not support data modification in parallel worker for now.
+     </para>
+
+     <para>
+      Tables should be labeled parallel dml unsafe/restricted if any parallel
+      unsafe/restricted function could be executed when modifying the data in
+      table (e.g., functions in trigger/index expression/constraints ...).
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable>query</replaceable></term>
     <listitem>
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f81bdf513b..e800a218b5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2203,6 +2203,7 @@ alter table test_storage alter column a set storage external;
  b      | integer |           |          | 0       | plain    |              | 
 Indexes:
     "test_storage_idx" btree (b, a)
+Parallel DML: unsafe
 
 \d+ test_storage_idx
                 Index "public.test_storage_idx"
@@ -4190,6 +4191,7 @@ ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
  a      | integer |           |          |         | plain   |              | 
 Partition key: RANGE (a)
 Number of partitions: 0
+Parallel DML: unsafe
 
 -- constraint should be created
 \d part_rp
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index 1ce2962d55..041e829b37 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -12,6 +12,7 @@ INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
  f1     | text |           |          |         | extended | pglz        |              | 
 Indexes:
     "idx" btree (f1)
+Parallel DML: unsafe
 
 CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
 ERROR:  compression method lz4 not supported
@@ -51,6 +52,7 @@ SELECT * INTO cmmove1 FROM cmdata;
  Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
 --------+------+-----------+----------+---------+----------+-------------+--------------+-------------
  f1     | text |           |          |         | extended |             |              | 
+Parallel DML: unsafe
 
 SELECT pg_column_compression(f1) FROM cmmove1;
  pg_column_compression 
@@ -138,6 +140,7 @@ CREATE TABLE cmdata2 (f1 int);
  Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
 --------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
  f1     | integer |           |          |         | plain   |             |              | 
+Parallel DML: unsafe
 
 ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
 \d+ cmdata2
@@ -145,6 +148,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
  Column |       Type        | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
 --------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
  f1     | character varying |           |          |         | extended |             |              | 
+Parallel DML: unsafe
 
 ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
 \d+ cmdata2
@@ -152,6 +156,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
  Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
 --------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
  f1     | integer |           |          |         | plain   |             |              | 
+Parallel DML: unsafe
 
 --changing column storage should not impact the compression method
 --but the data should not be compressed
@@ -162,6 +167,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz;
  Column |       Type        | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
 --------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
  f1     | character varying |           |          |         | extended | pglz        |              | 
+Parallel DML: unsafe
 
 ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
 \d+ cmdata2
@@ -169,6 +175,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
  Column |       Type        | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
 --------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
  f1     | character varying |           |          |         | plain   | pglz        |              | 
+Parallel DML: unsafe
 
 INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
 SELECT pg_column_compression(f1) FROM cmdata2;
@@ -249,6 +256,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
  f1     | text |           |          |         | extended | pglz        |              | 
 Indexes:
     "idx" btree (f1)
+Parallel DML: unsafe
 
 SELECT pg_column_compression(f1) FROM cmdata;
  pg_column_compression 
@@ -263,6 +271,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
  Column |       Type        | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
 --------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
  f1     | character varying |           |          |         | plain   |             |              | 
+Parallel DML: unsafe
 
 -- test alter compression method for materialized views
 ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c64f0719e7..df20bcb97b 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -517,6 +517,7 @@ alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
  f1     | integer |           |          |         | plain   |              | 
 Check constraints:
     "check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+Parallel DML: unsafe
 
 copy check_con_tbl from stdin;
 NOTICE:  input = {"f1":1}
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 96bf426d98..13e2c1ca66 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,6 +505,7 @@ Number of partitions: 0
  b      | text    |           |          |         | extended |              | 
 Partition key: RANGE (((a + 1)), substr(b, 1, 5))
 Number of partitions: 0
+Parallel DML: unsafe
 
 INSERT INTO partitioned2 VALUES (1, 'hello');
 ERROR:  no partition of relation "partitioned2" found for row
@@ -518,6 +519,7 @@ CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO
  b      | text    |           |          |         | extended |              | 
 Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
 Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+Parallel DML: unsafe
 
 DROP TABLE partitioned, partitioned2;
 -- check reference to partitioned table's rowtype in partition descriptor
@@ -559,6 +561,7 @@ select * from partitioned where partitioned = '(1,2)'::partitioned;
  b      | integer |           |          |         | plain   |              | 
 Partition of: partitioned FOR VALUES IN ('(1,2)')
 Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+Parallel DML: unsafe
 
 drop table partitioned;
 -- check that dependencies of partition columns are handled correctly
@@ -618,6 +621,7 @@ Partitions: part_null FOR VALUES IN (NULL),
             part_p1 FOR VALUES IN (1),
             part_p2 FOR VALUES IN (2),
             part_p3 FOR VALUES IN (3)
+Parallel DML: unsafe
 
 -- forbidden expressions for partition bound with list partitioned table
 CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
@@ -1064,6 +1068,7 @@ drop table test_part_coll_posix;
  b      | integer |           | not null | 1       | plain    |              | 
 Partition of: parted FOR VALUES IN ('b')
 Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Parallel DML: unsafe
 
 -- Both partition bound and partition key in describe output
 \d+ part_c
@@ -1076,6 +1081,7 @@ Partition of: parted FOR VALUES IN ('c')
 Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
 Partition key: RANGE (b)
 Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+Parallel DML: unsafe
 
 -- a level-2 partition's constraint will include the parent's expressions
 \d+ part_c_1_10
@@ -1086,6 +1092,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
  b      | integer |           | not null | 0       | plain    |              | 
 Partition of: part_c FOR VALUES FROM (1) TO (10)
 Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Parallel DML: unsafe
 
 -- Show partition count in the parent's describe output
 -- Tempted to include \d+ output listing partitions with bound info but
@@ -1120,6 +1127,7 @@ CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MI
  c      | integer |           |          |         | plain   |              | 
 Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+Parallel DML: unsafe
 
 DROP TABLE unbounded_range_part;
 CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
@@ -1132,6 +1140,7 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALU
  c      | integer |           |          |         | plain   |              | 
 Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Parallel DML: unsafe
 
 CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
 \d+ range_parted4_2
@@ -1143,6 +1152,7 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5
  c      | integer |           |          |         | plain   |              | 
 Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Parallel DML: unsafe
 
 CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
 \d+ range_parted4_3
@@ -1154,6 +1164,7 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
  c      | integer |           |          |         | plain   |              | 
 Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Parallel DML: unsafe
 
 DROP TABLE range_parted4;
 -- user-defined operator class in partition key
@@ -1190,6 +1201,7 @@ SELECT obj_description('parted_col_comment'::regclass);
  b      | text    |           |          |         | extended |              | 
 Partition key: LIST (a)
 Number of partitions: 0
+Parallel DML: unsafe
 
 DROP TABLE parted_col_comment;
 -- list partitioning on array type column
@@ -1202,6 +1214,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
  a      | integer[] |           |          |         | extended |              | 
 Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
 Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+Parallel DML: unsafe
 
 DROP TABLE arrlp;
 -- partition on boolean column
@@ -1216,6 +1229,7 @@ create table boolspart_f partition of boolspart for values in (false);
 Partition key: LIST (a)
 Partitions: boolspart_f FOR VALUES IN (false),
             boolspart_t FOR VALUES IN (true)
+Parallel DML: unsafe
 
 drop table boolspart;
 -- partitions mixing temporary and permanent relations
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 4dc5e6aa5f..af8de78bdd 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -333,6 +333,7 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
  a      | text |           | not null |         | main     |              | 
  b      | text |           |          |         | extended |              | 
  c      | text |           |          |         | external |              | 
+Parallel DML: unsafe
 
 CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
 \d+ ctlt12_comments
@@ -342,6 +343,7 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
  a      | text |           | not null |         | extended |              | A
  b      | text |           |          |         | extended |              | B
  c      | text |           |          |         | extended |              | C
+Parallel DML: unsafe
 
 CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
 NOTICE:  merging column "a" with inherited definition
@@ -356,6 +358,7 @@ NOTICE:  merging constraint "ctlt1_a_check" with inherited definition
 Check constraints:
     "ctlt1_a_check" CHECK (length(a) > 2)
 Inherits: ctlt1
+Parallel DML: unsafe
 
 SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
  description 
@@ -378,6 +381,7 @@ Check constraints:
     "ctlt3_c_check" CHECK (length(c) < 7)
 Inherits: ctlt1,
           ctlt3
+Parallel DML: unsafe
 
 CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
 NOTICE:  merging column "a" with inherited definition
@@ -395,6 +399,7 @@ Check constraints:
     "ctlt3_a_check" CHECK (length(a) < 5)
     "ctlt3_c_check" CHECK (length(c) < 7)
 Inherits: ctlt1
+Parallel DML: unsafe
 
 SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
  description 
@@ -418,6 +423,7 @@ Check constraints:
 Statistics objects:
     "public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
     "public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
+Parallel DML: unsafe
 
 SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
     relname     | objsubid | description 
@@ -458,6 +464,7 @@ Check constraints:
 Statistics objects:
     "public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
     "public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
+Parallel DML: unsafe
 
 DROP TABLE public.pg_attrdef;
 -- Check that LIKE isn't confused when new table masks the old, either
@@ -480,6 +487,7 @@ Check constraints:
 Statistics objects:
     "ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
     "ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
+Parallel DML: unsafe
 
 ROLLBACK;
 DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..2419d96a33 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -276,6 +276,7 @@ Rules:
     silly AS
     ON DELETE TO dcomptable DO INSTEAD  UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
   WHERE (dcomptable.d1).i > 0::double precision
+Parallel DML: unsafe
 
 drop table dcomptable;
 drop type comptype cascade;
@@ -413,6 +414,7 @@ Rules:
     silly AS
     ON DELETE TO dcomptable DO INSTEAD  UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
   WHERE dcomptable.d1[1].i > 0::double precision
+Parallel DML: unsafe
 
 drop table dcomptable;
 drop type comptype cascade;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 5385f98a0f..4f50410f39 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -731,6 +731,7 @@ Check constraints:
     "ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 \det+
                                  List of foreign tables
@@ -852,6 +853,7 @@ Check constraints:
     "ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 -- can't change the column type if it's used elsewhere
 CREATE TABLE use_ft1_column_type (x ft1);
@@ -1390,6 +1392,7 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
  c2     | text    |           |          |         | extended |              | 
  c3     | date    |           |          |         | plain    |              | 
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1401,6 +1404,7 @@ Child tables: ft2
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
+Parallel DML: unsafe
 
 DROP FOREIGN TABLE ft2;
 \d+ fd_pt1
@@ -1410,6 +1414,7 @@ DROP FOREIGN TABLE ft2;
  c1     | integer |           | not null |         | plain    |              | 
  c2     | text    |           |          |         | extended |              | 
  c3     | date    |           |          |         | plain    |              | 
+Parallel DML: unsafe
 
 CREATE FOREIGN TABLE ft2 (
 	c1 integer NOT NULL,
@@ -1425,6 +1430,7 @@ CREATE FOREIGN TABLE ft2 (
  c3     | date    |           |          |         |             | plain    |              | 
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
 \d+ fd_pt1
@@ -1435,6 +1441,7 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
  c2     | text    |           |          |         | extended |              | 
  c3     | date    |           |          |         | plain    |              | 
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1446,6 +1453,7 @@ Child tables: ft2
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
+Parallel DML: unsafe
 
 CREATE TABLE ct3() INHERITS(ft2);
 CREATE FOREIGN TABLE ft3 (
@@ -1469,6 +1477,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
 Child tables: ct3,
               ft3
+Parallel DML: unsafe
 
 \d+ ct3
                                     Table "public.ct3"
@@ -1478,6 +1487,7 @@ Child tables: ct3,
  c2     | text    |           |          |         | extended |              | 
  c3     | date    |           |          |         | plain    |              | 
 Inherits: ft2
+Parallel DML: unsafe
 
 \d+ ft3
                                        Foreign table "public.ft3"
@@ -1488,6 +1498,7 @@ Inherits: ft2
  c3     | date    |           |          |         |             | plain    |              | 
 Server: s0
 Inherits: ft2
+Parallel DML: unsafe
 
 -- add attributes recursively
 ALTER TABLE fd_pt1 ADD COLUMN c4 integer;
@@ -1508,6 +1519,7 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
  c7     | integer |           | not null |         | plain    |              | 
  c8     | integer |           |          |         | plain    |              | 
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1526,6 +1538,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
 Child tables: ct3,
               ft3
+Parallel DML: unsafe
 
 \d+ ct3
                                     Table "public.ct3"
@@ -1540,6 +1553,7 @@ Child tables: ct3,
  c7     | integer |           | not null |         | plain    |              | 
  c8     | integer |           |          |         | plain    |              | 
 Inherits: ft2
+Parallel DML: unsafe
 
 \d+ ft3
                                        Foreign table "public.ft3"
@@ -1555,6 +1569,7 @@ Inherits: ft2
  c8     | integer |           |          |         |             | plain    |              | 
 Server: s0
 Inherits: ft2
+Parallel DML: unsafe
 
 -- alter attributes recursively
 ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0;
@@ -1582,6 +1597,7 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
  c7     | integer |           |          |         | plain    |              | 
  c8     | text    |           |          |         | external |              | 
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1600,6 +1616,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
 Child tables: ct3,
               ft3
+Parallel DML: unsafe
 
 -- drop attributes recursively
 ALTER TABLE fd_pt1 DROP COLUMN c4;
@@ -1615,6 +1632,7 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
  c2     | text    |           |          |         | extended |              | 
  c3     | date    |           |          |         | plain    |              | 
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1628,6 +1646,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
 Child tables: ct3,
               ft3
+Parallel DML: unsafe
 
 -- add constraints recursively
 ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT;
@@ -1655,6 +1674,7 @@ Check constraints:
     "fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
     "fd_pt1chk2" CHECK (c2 <> ''::text)
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1670,6 +1690,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
 Child tables: ct3,
               ft3
+Parallel DML: unsafe
 
 DROP FOREIGN TABLE ft2; -- ERROR
 ERROR:  cannot drop foreign table ft2 because other objects depend on it
@@ -1702,6 +1723,7 @@ Check constraints:
     "fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
     "fd_pt1chk2" CHECK (c2 <> ''::text)
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1715,6 +1737,7 @@ Check constraints:
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
+Parallel DML: unsafe
 
 -- drop constraints recursively
 ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE;
@@ -1732,6 +1755,7 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
 Check constraints:
     "fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1746,6 +1770,7 @@ Check constraints:
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
+Parallel DML: unsafe
 
 -- VALIDATE CONSTRAINT need do nothing on foreign tables
 ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
@@ -1759,6 +1784,7 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
 Check constraints:
     "fd_pt1chk3" CHECK (c2 <> ''::text)
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1773,6 +1799,7 @@ Check constraints:
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
+Parallel DML: unsafe
 
 -- changes name of an attribute recursively
 ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1;
@@ -1790,6 +1817,7 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
 Check constraints:
     "f2_check" CHECK (f2 <> ''::text)
 Child tables: ft2
+Parallel DML: unsafe
 
 \d+ ft2
                                        Foreign table "public.ft2"
@@ -1804,6 +1832,7 @@ Check constraints:
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 Inherits: fd_pt1
+Parallel DML: unsafe
 
 -- TRUNCATE doesn't work on foreign tables, either directly or recursively
 TRUNCATE ft2;  -- ERROR
@@ -1853,6 +1882,7 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
  c3     | date    |           |          |         | plain    |              | 
 Partition key: LIST (c1)
 Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
 
 \d+ fd_pt2_1
                                      Foreign table "public.fd_pt2_1"
@@ -1865,6 +1895,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
 Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 -- partition cannot have additional columns
 DROP FOREIGN TABLE fd_pt2_1;
@@ -1884,6 +1915,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
  c4     | character(1) |           |          |         |             | extended |              | 
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);       -- ERROR
 ERROR:  table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
@@ -1898,6 +1930,7 @@ DROP FOREIGN TABLE fd_pt2_1;
  c3     | date    |           |          |         | plain    |              | 
 Partition key: LIST (c1)
 Number of partitions: 0
+Parallel DML: unsafe
 
 CREATE FOREIGN TABLE fd_pt2_1 (
 	c1 integer NOT NULL,
@@ -1913,6 +1946,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
  c3     | date    |           |          |         |             | plain    |              | 
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 -- no attach partition validation occurs for foreign tables
 ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
@@ -1925,6 +1959,7 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
  c3     | date    |           |          |         | plain    |              | 
 Partition key: LIST (c1)
 Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
 
 \d+ fd_pt2_1
                                      Foreign table "public.fd_pt2_1"
@@ -1937,6 +1972,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
 Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 -- cannot add column to a partition
 ALTER TABLE fd_pt2_1 ADD c4 char;
@@ -1953,6 +1989,7 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
  c3     | date    |           |          |         | plain    |              | 
 Partition key: LIST (c1)
 Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
 
 \d+ fd_pt2_1
                                      Foreign table "public.fd_pt2_1"
@@ -1967,6 +2004,7 @@ Check constraints:
     "p21chk" CHECK (c2 <> ''::text)
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 -- cannot drop inherited NOT NULL constraint from a partition
 ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
@@ -1983,6 +2021,7 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
  c3     | date    |           |          |         | plain    |              | 
 Partition key: LIST (c1)
 Number of partitions: 0
+Parallel DML: unsafe
 
 \d+ fd_pt2_1
                                      Foreign table "public.fd_pt2_1"
@@ -1995,6 +2034,7 @@ Check constraints:
     "p21chk" CHECK (c2 <> ''::text)
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);       -- ERROR
 ERROR:  column "c2" in child table must be marked NOT NULL
@@ -2013,6 +2053,7 @@ Partition key: LIST (c1)
 Check constraints:
     "fd_pt2chk1" CHECK (c1 > 0)
 Number of partitions: 0
+Parallel DML: unsafe
 
 \d+ fd_pt2_1
                                      Foreign table "public.fd_pt2_1"
@@ -2025,6 +2066,7 @@ Check constraints:
     "p21chk" CHECK (c2 <> ''::text)
 Server: s0
 FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
 
 ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);       -- ERROR
 ERROR:  child table is missing constraint "fd_pt2chk1"
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7..da24c16d09 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -506,6 +506,7 @@ TABLE itest8;
  f3     | integer |           | not null | generated by default as identity | plain   |              | 
  f4     | bigint  |           | not null | generated always as identity     | plain   |              | 
  f5     | bigint  |           |          |                                  | plain   |              | 
+Parallel DML: unsafe
 
 \d itest8_f2_seq
                    Sequence "public.itest8_f2_seq"
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 06f44287bc..33a216ea4d 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1059,6 +1059,7 @@ ALTER TABLE inhts RENAME d TO dd;
  dd     | integer |           |          |         | plain   |              | 
 Inherits: inht1,
           inhs1
+Parallel DML: unsafe
 
 DROP TABLE inhts;
 -- Test for renaming in diamond inheritance
@@ -1079,6 +1080,7 @@ ALTER TABLE inht1 RENAME aa TO aaa;
  z      | integer |           |          |         | plain   |              | 
 Inherits: inht2,
           inht3
+Parallel DML: unsafe
 
 CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
 NOTICE:  merging multiple inherited definitions of column "b"
@@ -1096,6 +1098,7 @@ ERROR:  cannot rename inherited column "b"
  d      | integer |           |          |         | plain   |              | 
 Inherits: inht2,
           inhs1
+Parallel DML: unsafe
 
 WITH RECURSIVE r AS (
   SELECT 'inht1'::regclass AS inhrelid
@@ -1142,6 +1145,7 @@ CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
 Indexes:
     "test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
 Child tables: test_constraints_inh
+Parallel DML: unsafe
 
 ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
 \d+ test_constraints
@@ -1152,6 +1156,7 @@ ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key
  val1   | character varying |           |          |         | extended |              | 
  val2   | integer           |           |          |         | plain    |              | 
 Child tables: test_constraints_inh
+Parallel DML: unsafe
 
 \d+ test_constraints_inh
                                  Table "public.test_constraints_inh"
@@ -1161,6 +1166,7 @@ Child tables: test_constraints_inh
  val1   | character varying |           |          |         | extended |              | 
  val2   | integer           |           |          |         | plain    |              | 
 Inherits: test_constraints
+Parallel DML: unsafe
 
 DROP TABLE test_constraints_inh;
 DROP TABLE test_constraints;
@@ -1177,6 +1183,7 @@ CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
 Indexes:
     "test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
 Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
 
 ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
 \d+ test_ex_constraints
@@ -1185,6 +1192,7 @@ ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
 --------+--------+-----------+----------+---------+---------+--------------+-------------
  c      | circle |           |          |         | plain   |              | 
 Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
 
 \d+ test_ex_constraints_inh
                          Table "public.test_ex_constraints_inh"
@@ -1192,6 +1200,7 @@ Child tables: test_ex_constraints_inh
 --------+--------+-----------+----------+---------+---------+--------------+-------------
  c      | circle |           |          |         | plain   |              | 
 Inherits: test_ex_constraints
+Parallel DML: unsafe
 
 DROP TABLE test_ex_constraints_inh;
 DROP TABLE test_ex_constraints;
@@ -1208,6 +1217,7 @@ Indexes:
     "test_primary_constraints_pkey" PRIMARY KEY, btree (id)
 Referenced by:
     TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Parallel DML: unsafe
 
 \d+ test_foreign_constraints
                          Table "public.test_foreign_constraints"
@@ -1217,6 +1227,7 @@ Referenced by:
 Foreign-key constraints:
     "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
 Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
 
 ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
 \d+ test_foreign_constraints
@@ -1225,6 +1236,7 @@ ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id
 --------+---------+-----------+----------+---------+---------+--------------+-------------
  id1    | integer |           |          |         | plain   |              | 
 Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
 
 \d+ test_foreign_constraints_inh
                        Table "public.test_foreign_constraints_inh"
@@ -1232,6 +1244,7 @@ Child tables: test_foreign_constraints_inh
 --------+---------+-----------+----------+---------+---------+--------------+-------------
  id1    | integer |           |          |         | plain   |              | 
 Inherits: test_foreign_constraints
+Parallel DML: unsafe
 
 DROP TABLE test_foreign_constraints_inh;
 DROP TABLE test_foreign_constraints;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..cb8cd958aa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -177,6 +177,7 @@ Rules:
     irule3 AS
     ON INSERT TO inserttest2 DO  INSERT INTO inserttest (f4[1].if1, f4[1].if2[2])  SELECT new.f1,
             new.f2
+Parallel DML: unsafe
 
 drop table inserttest2;
 drop table inserttest;
@@ -482,6 +483,7 @@ Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
             part_null FOR VALUES IN (NULL),
             part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
             part_default DEFAULT, PARTITIONED
+Parallel DML: unsafe
 
 -- cleanup
 drop table range_parted, list_parted;
@@ -497,6 +499,7 @@ create table part_default partition of list_parted default;
  a      | integer |           |          |         | plain   |              | 
 Partition of: list_parted DEFAULT
 No partition constraint
+Parallel DML: unsafe
 
 insert into part_default values (null);
 insert into part_default values (1);
@@ -888,6 +891,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
             mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
             mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
             mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Parallel DML: unsafe
 
 \d+ mcrparted1_lt_b
                               Table "public.mcrparted1_lt_b"
@@ -897,6 +901,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
  b      | integer |           |          |         | plain    |              | 
 Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+Parallel DML: unsafe
 
 \d+ mcrparted2_b
                                 Table "public.mcrparted2_b"
@@ -906,6 +911,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
  b      | integer |           |          |         | plain    |              | 
 Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+Parallel DML: unsafe
 
 \d+ mcrparted3_c_to_common
                            Table "public.mcrparted3_c_to_common"
@@ -915,6 +921,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text)
  b      | integer |           |          |         | plain    |              | 
 Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+Parallel DML: unsafe
 
 \d+ mcrparted4_common_lt_0
                            Table "public.mcrparted4_common_lt_0"
@@ -924,6 +931,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text)
  b      | integer |           |          |         | plain    |              | 
 Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+Parallel DML: unsafe
 
 \d+ mcrparted5_common_0_to_10
                          Table "public.mcrparted5_common_0_to_10"
@@ -933,6 +941,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
  b      | integer |           |          |         | plain    |              | 
 Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+Parallel DML: unsafe
 
 \d+ mcrparted6_common_ge_10
                           Table "public.mcrparted6_common_ge_10"
@@ -942,6 +951,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
  b      | integer |           |          |         | plain    |              | 
 Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+Parallel DML: unsafe
 
 \d+ mcrparted7_gt_common_lt_d
                          Table "public.mcrparted7_gt_common_lt_d"
@@ -951,6 +961,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
  b      | integer |           |          |         | plain    |              | 
 Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+Parallel DML: unsafe
 
 \d+ mcrparted8_ge_d
                               Table "public.mcrparted8_ge_d"
@@ -960,6 +971,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::te
  b      | integer |           |          |         | plain    |              | 
 Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+Parallel DML: unsafe
 
 insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
     ('comm', -10), ('common', -10), ('common', 0), ('common', 10),
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..f5d22ce649
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,588 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+    begin
+        return f || l;
+    end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+    begin
+        return f || l;
+    end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+    (1, 'albert', 'einstein'),
+    (2, 'niels', 'bohr'),
+    (3, 'erwin', 'schrodinger'),
+    (4, 'leonhard', 'euler'),
+    (5, 'stephen', 'hawking'),
+    (6, 'isaac', 'newton'),
+    (7, 'alan', 'turing'),
+    (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+	RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+	RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+	RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+--
+-- END: setup some tables and data needed by the tests.
+--
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+create table para_insert_p1 (
+    unique1    int4 PRIMARY KEY,
+    stringu1    name
+);
+create table para_insert_f1 (
+    unique1    int4 REFERENCES para_insert_p1(unique1),
+    stringu1    name
+);
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+ pg_class_relname | proparallel 
+------------------+-------------
+ pg_proc          | r
+ pg_trigger       | r
+ pg_proc          | r
+ pg_trigger       | r
+(4 rows)
+
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+ pg_get_table_max_parallel_dml_hazard 
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+               QUERY PLAN               
+----------------------------------------
+ Insert on para_insert_p1
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count |   sum    
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count 
+-------
+     1
+(1 row)
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+        QUERY PLAN        
+--------------------------
+ Insert on para_insert_p1
+   ->  Seq Scan on tenk1
+(2 rows)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE:  truncate cascades to table "para_insert_f1"
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+                  QUERY PLAN                  
+----------------------------------------------
+ Insert on para_insert_p1
+   ->  Gather Merge
+         Workers Planned: 4
+         ->  Sort
+               Sort Key: tenk1.unique1
+               ->  Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count |   sum    
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count 
+-------
+     1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+                 QUERY PLAN                 
+--------------------------------------------
+ Insert on test_data1
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Seq Scan on test_data
+               Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data 
+------
+   10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+--  as doing this in a parallel worker would create a new commandId
+--  and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+               QUERY PLAN               
+----------------------------------------
+ Insert on para_insert_f1
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count |   sum    
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+                 QUERY PLAN                 
+--------------------------------------------
+ Insert on test_conflict_table
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Insert on test_conflict_table
+   Conflict Resolution: UPDATE
+   Conflict Arbiter Indexes: test_conflict_table_pkey
+   ->  Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+ pg_class_relname | proparallel 
+------------------+-------------
+ pg_proc          | u
+ pg_index         | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names2');
+ pg_get_table_max_parallel_dml_hazard 
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+ pg_class_relname | proparallel 
+------------------+-------------
+ pg_proc          | r
+ pg_index         | r
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names4');
+ pg_get_table_max_parallel_dml_hazard 
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+               QUERY PLAN               
+----------------------------------------
+ Insert on names5
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Seq Scan on names
+(4 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+                  QUERY PLAN                  
+----------------------------------------------
+ Insert on names6
+   ->  Gather Merge
+         Workers Planned: 3
+         ->  Sort
+               Sort Key: names.last_name
+               ->  Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name |  last_name  
+-------+------------+-------------
+     2 | niels      | bohr
+     1 | albert     | einstein
+     4 | leonhard   | euler
+     8 | richard    | feynman
+     5 | stephen    | hawking
+     6 | isaac      | newton
+     3 | erwin      | schrodinger
+     7 | alan       | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+                  QUERY PLAN                  
+----------------------------------------------
+ Insert on names7
+   ->  Gather Merge
+         Workers Planned: 3
+         ->  Sort
+               Sort Key: names.last_name
+               ->  Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name 
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+ pg_class_relname | proparallel 
+------------------+-------------
+ pg_class         | r
+(1 row)
+
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+ pg_get_table_max_parallel_dml_hazard 
+--------------------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+               QUERY PLAN               
+----------------------------------------
+ Insert on temp_names
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+         QUERY PLAN          
+-----------------------------
+ Insert on testdef
+   ->  Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+                 QUERY PLAN                 
+--------------------------------------------
+ Insert on testdef
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a  | b  | c  | d  
+----+----+----+----
+  1 |  2 | 10 |  8
+  2 |  4 | 10 | 16
+  3 |  6 | 10 | 24
+  4 |  8 | 10 | 32
+  5 | 10 | 10 | 40
+  6 | 12 | 10 | 48
+  7 | 14 | 10 | 56
+  8 | 16 | 10 | 64
+  9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+         QUERY PLAN          
+-----------------------------
+ Insert on testdef
+   ->  Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+               QUERY PLAN               
+----------------------------------------
+ Insert on parttable1
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count 
+-------
+  5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count 
+-------
+  5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+    begin
+        return (b <> 'XXXXXX');
+    end;
+$$ language plpgsql parallel unsafe;
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+ pg_class_relname | proparallel 
+------------------+-------------
+ pg_proc          | u
+ pg_constraint    | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+ pg_get_table_max_parallel_dml_hazard 
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+    begin
+        raise notice 'hello from insert_before_trigger_safe';
+		return new;
+    end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+    for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel 
+------------------+-------------
+(0 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+ pg_get_table_max_parallel_dml_hazard 
+--------------------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE:  hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+    begin
+        raise notice 'hello from insert_before_trigger_unsafe';
+		return new;
+    end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+    for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel 
+------------------+-------------
+ pg_proc          | u
+ pg_trigger       | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard 
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+    for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel 
+------------------+-------------
+ pg_proc          | u
+ pg_trigger       | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard 
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+  check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+ pg_class_relname | proparallel 
+------------------+-------------
+ pg_proc          | u
+ pg_constraint    | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+ pg_get_table_max_parallel_dml_hazard 
+--------------------------------------
+ u
+(1 row)
+
+rollback;
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1901673622 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2818,6 +2818,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
 --------+----------------+-----------+----------+---------+----------+--------------+-------------
  f1     | integer        |           |          |         | plain    |              | 
  f2     | character(100) |           |          |         | extended |              | 
+Parallel DML: unsafe
 
 \d+ tbl_heap
                                  Table "tableam_display.tbl_heap"
@@ -2825,6 +2826,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
 --------+----------------+-----------+----------+---------+----------+--------------+-------------
  f1     | integer        |           |          |         | plain    |              | 
  f2     | character(100) |           |          |         | extended |              | 
+Parallel DML: unsafe
 
 \set HIDE_TABLEAM off
 \d+ tbl_heap_psql
@@ -2834,6 +2836,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
  f1     | integer        |           |          |         | plain    |              | 
  f2     | character(100) |           |          |         | extended |              | 
 Access method: heap_psql
+Parallel DML: unsafe
 
 \d+ tbl_heap
                                  Table "tableam_display.tbl_heap"
@@ -2842,50 +2845,51 @@ Access method: heap_psql
  f1     | integer        |           |          |         | plain    |              | 
  f2     | character(100) |           |          |         | extended |              | 
 Access method: heap
+Parallel DML: unsafe
 
 -- AM is displayed for tables, indexes and materialized views.
 \d+
-                                                           List of relations
-     Schema      |        Name        |       Type        |        Owner         | Persistence | Access method |  Size   | Description 
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent   | heap_psql     | 0 bytes | 
- tableam_display | tbl_heap           | table             | regress_display_role | permanent   | heap          | 0 bytes | 
- tableam_display | tbl_heap_psql      | table             | regress_display_role | permanent   | heap_psql     | 0 bytes | 
- tableam_display | view_heap_psql     | view              | regress_display_role | permanent   |               | 0 bytes | 
+                                                                  List of relations
+     Schema      |        Name        |       Type        |        Owner         | Persistence | Access method | Parallel DML |  Size   | Description 
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent   | heap_psql     | unsafe       | 0 bytes | 
+ tableam_display | tbl_heap           | table             | regress_display_role | permanent   | heap          | unsafe       | 0 bytes | 
+ tableam_display | tbl_heap_psql      | table             | regress_display_role | permanent   | heap_psql     | unsafe       | 0 bytes | 
+ tableam_display | view_heap_psql     | view              | regress_display_role | permanent   |               | unsafe       | 0 bytes | 
 (4 rows)
 
 \dt+
-                                                  List of relations
-     Schema      |     Name      | Type  |        Owner         | Persistence | Access method |  Size   | Description 
------------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
- tableam_display | tbl_heap      | table | regress_display_role | permanent   | heap          | 0 bytes | 
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent   | heap_psql     | 0 bytes | 
+                                                          List of relations
+     Schema      |     Name      | Type  |        Owner         | Persistence | Access method | Parallel DML |  Size   | Description 
+-----------------+---------------+-------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | tbl_heap      | table | regress_display_role | permanent   | heap          | unsafe       | 0 bytes | 
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent   | heap_psql     | unsafe       | 0 bytes | 
 (2 rows)
 
 \dm+
-                                                           List of relations
-     Schema      |        Name        |       Type        |        Owner         | Persistence | Access method |  Size   | Description 
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent   | heap_psql     | 0 bytes | 
+                                                                  List of relations
+     Schema      |        Name        |       Type        |        Owner         | Persistence | Access method | Parallel DML |  Size   | Description 
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent   | heap_psql     | unsafe       | 0 bytes | 
 (1 row)
 
 -- But not for views and sequences.
 \dv+
-                                          List of relations
-     Schema      |      Name      | Type |        Owner         | Persistence |  Size   | Description 
------------------+----------------+------+----------------------+-------------+---------+-------------
- tableam_display | view_heap_psql | view | regress_display_role | permanent   | 0 bytes | 
+                                                  List of relations
+     Schema      |      Name      | Type |        Owner         | Persistence | Parallel DML |  Size   | Description 
+-----------------+----------------+------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent   | unsafe       | 0 bytes | 
 (1 row)
 
 \set HIDE_TABLEAM on
 \d+
-                                                   List of relations
-     Schema      |        Name        |       Type        |        Owner         | Persistence |  Size   | Description 
------------------+--------------------+-------------------+----------------------+-------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent   | 0 bytes | 
- tableam_display | tbl_heap           | table             | regress_display_role | permanent   | 0 bytes | 
- tableam_display | tbl_heap_psql      | table             | regress_display_role | permanent   | 0 bytes | 
- tableam_display | view_heap_psql     | view              | regress_display_role | permanent   | 0 bytes | 
+                                                          List of relations
+     Schema      |        Name        |       Type        |        Owner         | Persistence | Parallel DML |  Size   | Description 
+-----------------+--------------------+-------------------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent   | unsafe       | 0 bytes | 
+ tableam_display | tbl_heap           | table             | regress_display_role | permanent   | unsafe       | 0 bytes | 
+ tableam_display | tbl_heap_psql      | table             | regress_display_role | permanent   | unsafe       | 0 bytes | 
+ tableam_display | view_heap_psql     | view              | regress_display_role | permanent   | unsafe       | 0 bytes | 
 (4 rows)
 
 RESET ROLE;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..314ec05dc1 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -83,6 +83,7 @@ Indexes:
     "testpub_tbl2_pkey" PRIMARY KEY, btree (id)
 Publications:
     "testpub_foralltables"
+Parallel DML: unsafe
 
 \dRp+ testpub_foralltables
                               Publication testpub_foralltables
@@ -196,6 +197,7 @@ Publications:
     "testpib_ins_trunct"
     "testpub_default"
     "testpub_fortbl"
+Parallel DML: unsafe
 
 \d+ testpub_tbl1
                                                 Table "public.testpub_tbl1"
@@ -209,6 +211,7 @@ Publications:
     "testpib_ins_trunct"
     "testpub_default"
     "testpub_fortbl"
+Parallel DML: unsafe
 
 \dRp+ testpub_default
                                 Publication testpub_default
@@ -234,6 +237,7 @@ Indexes:
 Publications:
     "testpib_ins_trunct"
     "testpub_fortbl"
+Parallel DML: unsafe
 
 -- permissions
 SET ROLE regress_publication_user2;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..0f8718f2a4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -171,6 +171,7 @@ Indexes:
     "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
     "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
 Replica Identity: FULL
+Parallel DML: unsafe
 
 ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
 SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 89397e41f0..396dc3a539 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -958,6 +958,7 @@ Policies:
 Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
             part_document_nonfiction FOR VALUES FROM (99) TO (100),
             part_document_satire FOR VALUES FROM (55) TO (56)
+Parallel DML: unsafe
 
 SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
      schemaname     |   tablename   | policyname | permissive  |       roles        | cmd |                    qual                    | with_check 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..cad4be0a8b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3155,6 +3155,7 @@ Rules:
     r3 AS
     ON DELETE TO rules_src DO
  NOTIFY rules_src_deletion
+Parallel DML: unsafe
 
 --
 -- Ensure an aliased target relation for insert is correctly deparsed.
@@ -3183,6 +3184,7 @@ Rules:
     r5 AS
     ON UPDATE TO rules_src DO INSTEAD  UPDATE rules_log trgt SET tag = 'updated'::text
   WHERE trgt.f1 = new.f1
+Parallel DML: unsafe
 
 --
 -- Also check multiassignment deparsing.
@@ -3206,6 +3208,7 @@ Rules:
   WHERE trgt.f1 = new.f1
   RETURNING new.f1,
     new.f2
+Parallel DML: unsafe
 
 drop table rule_t1, rule_dest;
 --
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c214d8dfc..c94eb3293e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -145,6 +145,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
  b      | integer |           |          |         | plain   |              | 
 Statistics objects:
     "public"."ab1_a_b_stats" ON a, b FROM ab1
+Parallel DML: unsafe
 
 -- partial analyze doesn't build stats either
 ANALYZE ab1 (a);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index c809f88f54..3fcd8e10f3 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -753,6 +753,7 @@ create table part_def partition of range_parted default;
  e      | character varying |           |          |         | extended |              | 
 Partition of: range_parted DEFAULT
 Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
+Parallel DML: unsafe
 
 insert into range_parted values ('c', 9);
 -- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..11a750b58d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -339,6 +339,7 @@ Indexes:
     "part_a_idx" btree (a), tablespace "regress_tblspace"
 Partitions: testschema.part1 FOR VALUES IN (1),
             testschema.part2 FOR VALUES IN (2)
+Parallel DML: unsafe
 
 \d testschema.part1
              Table "testschema.part1"
@@ -358,6 +359,7 @@ Partition of: testschema.part FOR VALUES IN (1)
 Partition constraint: ((a IS NOT NULL) AND (a = 1))
 Indexes:
     "part1_a_idx" btree (a), tablespace "regress_tblspace"
+Parallel DML: unsafe
 
 \d testschema.part_a_idx
 Partitioned index "testschema.part_a_idx"
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
 # run by itself so it can run parallel workers
 test: select_parallel
 test: write_parallel
+test: insert_parallel
 
 # no relation related tests can be put in this group
 test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..b4565f78ab
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,345 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+    begin
+        return f || l;
+    end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+    begin
+        return f || l;
+    end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+    (1, 'albert', 'einstein'),
+    (2, 'niels', 'bohr'),
+    (3, 'erwin', 'schrodinger'),
+    (4, 'leonhard', 'euler'),
+    (5, 'stephen', 'hawking'),
+    (6, 'isaac', 'newton'),
+    (7, 'alan', 'turing'),
+    (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+	RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+	RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+	RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+begin;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+create table para_insert_p1 (
+    unique1    int4 PRIMARY KEY,
+    stringu1    name
+);
+
+create table para_insert_f1 (
+    unique1    int4 REFERENCES para_insert_p1(unique1),
+    stringu1    name
+);
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+--  as doing this in a parallel worker would create a new commandId
+--  and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+select pg_get_table_max_parallel_dml_hazard('names2');
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+select pg_get_table_max_parallel_dml_hazard('names4');
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+    begin
+        return (b <> 'XXXXXX');
+    end;
+$$ language plpgsql parallel unsafe;
+
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+    begin
+        raise notice 'hello from insert_before_trigger_safe';
+		return new;
+    end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+    for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+    begin
+        raise notice 'hello from insert_before_trigger_unsafe';
+		return new;
+    end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+    for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+    for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+  check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+
+rollback;
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
-- 
2.18.4

