From 529877a574ca2584545b999f43c2bb94cc4dc78c Mon Sep 17 00:00:00 2001
From: Greg Nancarrow <gregn4422@gmail.com>
Date: Wed, 20 Jan 2021 21:52:46 +1100
Subject: [PATCH v12 2/4] Parallel SELECT for "INSERT INTO ... SELECT ..." -
 tests and documentation updates.

---
 doc/src/sgml/parallel.sgml                    |    4 +-
 src/test/regress/expected/insert_parallel.out | 1078 +++++++++++++++++
 src/test/regress/parallel_schedule            |    1 +
 src/test/regress/serial_schedule              |    1 +
 src/test/regress/sql/insert_parallel.sql      |  531 ++++++++
 5 files changed, 1614 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/insert_parallel.out
 create mode 100644 src/test/regress/sql/insert_parallel.sql

diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index c81abff48d..cec1329e25 100644
--- a/doc/src/sgml/parallel.sgml
+++ b/doc/src/sgml/parallel.sgml
@@ -146,7 +146,9 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
         a CTE, no parallel plans for that query will be generated.  As an
         exception, the commands <literal>CREATE TABLE ... AS</literal>, <literal>SELECT
         INTO</literal>, and <literal>CREATE MATERIALIZED VIEW</literal> which create a new
-        table and populate it can use a parallel plan.
+        table and populate it can use a parallel plan. Another exception is the command
+        <literal>INSERT INTO ... SELECT ...</literal> which can use a parallel plan for
+        the underlying <literal>SELECT</literal> part of the query.
       </para>
     </listitem>
 
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..71d63d79b6
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,1078 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+-- 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_safe(f text, l text) returns text as $$
+    begin
+        return f || l;
+    end;
+$$ language plpgsql immutable parallel safe;
+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 names3(index int, first_name text, last_name text);
+create index names3_fullname_idx on names3 (fullname_parallel_safe(first_name, last_name));
+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.
+--
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+begin isolation level repeatable read;
+-- 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
+);
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+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)
+
+select * from para_insert_p1 where unique1 >= 9990 order by unique1;
+ unique1 | stringu1 
+---------+----------
+    9990 | GUAAAA
+    9991 | HUAAAA
+    9992 | IUAAAA
+    9993 | JUAAAA
+    9994 | KUAAAA
+    9995 | LUAAAA
+    9996 | MUAAAA
+    9997 | NUAAAA
+    9998 | OUAAAA
+    9999 | PUAAAA
+(10 rows)
+
+-- 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 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"
+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)
+
+select * from para_insert_p1 where unique1 >= 9990 order by unique1;
+ unique1 | stringu1 
+---------+----------
+    9990 | GUAAAA
+    9991 | HUAAAA
+    9992 | IUAAAA
+    9993 | JUAAAA
+    9994 | KUAAAA
+    9995 | LUAAAA
+    9996 | MUAAAA
+    9997 | NUAAAA
+    9998 | OUAAAA
+    9999 | PUAAAA
+(10 rows)
+
+-- 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 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)
+--
+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)
+
+select * from para_insert_f1 where unique1 >= 9990 order by unique1;
+ unique1 | stringu1 
+---------+----------
+    9990 | GUAAAA
+    9991 | HUAAAA
+    9992 | IUAAAA
+    9993 | JUAAAA
+    9994 | KUAAAA
+    9995 | LUAAAA
+    9996 | MUAAAA
+    9997 | NUAAAA
+    9998 | OUAAAA
+    9999 | PUAAAA
+(10 rows)
+
+--
+-- Test INSERT with underlying query, leader participation disabled
+--
+set parallel_leader_participation = off;
+truncate para_insert_p1 cascade;
+NOTICE:  truncate cascades to table "para_insert_f1"
+explain(costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 <= 2500;
+               QUERY PLAN                
+-----------------------------------------
+ Insert on para_insert_p1
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Seq Scan on tenk1
+               Filter: (unique1 <= 2500)
+(5 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 <= 2500;
+select count(*), sum(unique1) from para_insert_p1;
+ count |   sum   
+-------+---------
+  2501 | 3126250
+(1 row)
+
+select * from para_insert_p1 where unique1 >= 2490 order by unique1;
+ unique1 | stringu1 
+---------+----------
+    2490 | URAAAA
+    2491 | VRAAAA
+    2492 | WRAAAA
+    2493 | XRAAAA
+    2494 | YRAAAA
+    2495 | ZRAAAA
+    2496 | ASAAAA
+    2497 | BSAAAA
+    2498 | CSAAAA
+    2499 | DSAAAA
+    2500 | ESAAAA
+(11 rows)
+
+--
+-- Test INSERT with underlying query, leader participation disabled
+--  and no workers available
+set max_parallel_workers=0;
+truncate para_insert_p1 cascade;
+NOTICE:  truncate cascades to table "para_insert_f1"
+explain(costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 <= 2500;
+               QUERY PLAN                
+-----------------------------------------
+ Insert on para_insert_p1
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Seq Scan on tenk1
+               Filter: (unique1 <= 2500)
+(5 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 <= 2500;
+select count(*), sum(unique1) from para_insert_p1;
+ count |   sum   
+-------+---------
+  2501 | 3126250
+(1 row)
+
+select * from para_insert_p1 where unique1 >= 2490 order by unique1;
+ unique1 | stringu1 
+---------+----------
+    2490 | URAAAA
+    2491 | VRAAAA
+    2492 | WRAAAA
+    2493 | XRAAAA
+    2494 | YRAAAA
+    2495 | ZRAAAA
+    2496 | ASAAAA
+    2497 | BSAAAA
+    2498 | CSAAAA
+    2499 | DSAAAA
+    2500 | ESAAAA
+(11 rows)
+
+reset parallel_leader_participation;
+reset max_parallel_workers;
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_data2(like test_data);
+insert into test_data2 select i from generate_series(1,10000) i;
+create table test_conflict_table(id serial primary key, somedata int);
+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)
+
+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 parallelized aggregate
+--
+create table tenk1_avg_data(count int, avg_unique1 int, avg_stringu1_len int);
+explain (costs off) insert into tenk1_avg_data select count(*), avg(unique1), avg(length(stringu1)) from tenk1;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Insert on tenk1_avg_data
+   ->  Subquery Scan on "*SELECT*"
+         ->  Finalize Aggregate
+               ->  Gather
+                     Workers Planned: 4
+                     ->  Partial Aggregate
+                           ->  Parallel Seq Scan on tenk1
+(7 rows)
+
+insert into tenk1_avg_data select count(*), avg(unique1), avg(length(stringu1)) from tenk1;
+select * from tenk1_avg_data;
+ count | avg_unique1 | avg_stringu1_len 
+-------+-------------+------------------
+ 10000 |        5000 |                6
+(1 row)
+
+--
+-- Test INSERT with parallel bitmap heap scan
+--
+set enable_seqscan to off;
+set enable_indexscan to off;
+truncate para_insert_p1 cascade;
+NOTICE:  truncate cascades to table "para_insert_f1"
+explain(costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 >= 7500;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Insert on para_insert_p1
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Bitmap Heap Scan on tenk1
+               Recheck Cond: (unique1 >= 7500)
+               ->  Bitmap Index Scan on tenk1_unique1
+                     Index Cond: (unique1 >= 7500)
+(7 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 >= 7500;
+-- select some values to verify that the insert worked
+select * from para_insert_p1 where unique1 >= 9990 order by unique1;
+ unique1 | stringu1 
+---------+----------
+    9990 | GUAAAA
+    9991 | HUAAAA
+    9992 | IUAAAA
+    9993 | JUAAAA
+    9994 | KUAAAA
+    9995 | LUAAAA
+    9996 | MUAAAA
+    9997 | NUAAAA
+    9998 | OUAAAA
+    9999 | PUAAAA
+(10 rows)
+
+reset enable_seqscan;
+reset enable_indexscan;
+--
+-- Test INSERT with parallel append
+--
+create table a_star_data(aa int);
+explain (costs off) insert into a_star_data select aa from a_star where aa > 10;
+                       QUERY PLAN                       
+--------------------------------------------------------
+ Insert on a_star_data
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Append
+               ->  Parallel Seq Scan on d_star a_star_4
+                     Filter: (aa > 10)
+               ->  Parallel Seq Scan on f_star a_star_6
+                     Filter: (aa > 10)
+               ->  Parallel Seq Scan on e_star a_star_5
+                     Filter: (aa > 10)
+               ->  Parallel Seq Scan on b_star a_star_2
+                     Filter: (aa > 10)
+               ->  Parallel Seq Scan on c_star a_star_3
+                     Filter: (aa > 10)
+               ->  Parallel Seq Scan on a_star a_star_1
+                     Filter: (aa > 10)
+(16 rows)
+
+insert into a_star_data select aa from a_star where aa > 10;
+select count(aa), sum(aa) from a_star_data;
+ count | sum 
+-------+-----
+    16 | 300
+(1 row)
+
+--
+-- Test INSERT with parallel index scan
+--
+set enable_seqscan to off;
+set enable_bitmapscan to off;
+set min_parallel_index_scan_size=0;
+truncate para_insert_p1 cascade;
+NOTICE:  truncate cascades to table "para_insert_f1"
+explain(costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 >= 500;
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Insert on para_insert_p1
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Index Scan using tenk1_unique1 on tenk1
+               Index Cond: (unique1 >= 500)
+(5 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 >= 500;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count |   sum    
+-------+----------
+  9500 | 49870250
+(1 row)
+
+select * from para_insert_p1 where unique1 >= 9990 order by unique1;
+ unique1 | stringu1 
+---------+----------
+    9990 | GUAAAA
+    9991 | HUAAAA
+    9992 | IUAAAA
+    9993 | JUAAAA
+    9994 | KUAAAA
+    9995 | LUAAAA
+    9996 | MUAAAA
+    9997 | NUAAAA
+    9998 | OUAAAA
+    9999 | PUAAAA
+(10 rows)
+
+--
+-- Test INSERT with parallel index-only scan
+--
+truncate para_insert_p1 cascade;
+NOTICE:  truncate cascades to table "para_insert_f1"
+explain(costs off) insert into para_insert_p1 select unique1 from tenk1 where unique1 >= 500;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Insert on para_insert_p1
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Index Only Scan using tenk1_unique1 on tenk1
+               Index Cond: (unique1 >= 500)
+(5 rows)
+
+insert into para_insert_p1 select unique1 from tenk1 where unique1 >= 500;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count |   sum    
+-------+----------
+  9500 | 49870250
+(1 row)
+
+select unique1 from para_insert_p1 where unique1 >= 9990 order by unique1;
+ unique1 
+---------
+    9990
+    9991
+    9992
+    9993
+    9994
+    9995
+    9996
+    9997
+    9998
+    9999
+(10 rows)
+
+reset min_parallel_index_scan_size;
+reset enable_seqscan;
+reset enable_bitmapscan;
+--
+-- Test INSERT with parallel-safe index expression
+-- (should create a parallel plan)
+--
+explain (costs off) insert into names3 select * from names;
+               QUERY PLAN               
+----------------------------------------
+ Insert on names3
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Seq Scan on names
+(4 rows)
+
+insert into names3 select * from names;
+select * from names3 order by fullname_parallel_safe(first_name, last_name);
+ index | first_name |  last_name  
+-------+------------+-------------
+     7 | alan       | turing
+     1 | albert     | einstein
+     3 | erwin      | schrodinger
+     6 | isaac      | newton
+     4 | leonhard   | euler
+     2 | niels      | bohr
+     8 | richard    | feynman
+     5 | stephen    | hawking
+(8 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+-- (should not create a parallel plan)
+--
+explain (costs off) insert into names2 select * from names;
+       QUERY PLAN        
+-------------------------
+ Insert on names2
+   ->  Seq Scan on names
+(2 rows)
+
+insert into names2 select * from names;
+select * from names2 order by fullname_parallel_unsafe(first_name, last_name);
+ index | first_name |  last_name  
+-------+------------+-------------
+     7 | alan       | turing
+     1 | albert     | einstein
+     3 | erwin      | schrodinger
+     6 | isaac      | newton
+     4 | leonhard   | euler
+     2 | niels      | bohr
+     8 | richard    | feynman
+     5 | stephen    | hawking
+(8 rows)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names4 (like names);
+explain (costs off) insert into names4 select * from names returning *;
+               QUERY PLAN               
+----------------------------------------
+ Insert on names4
+   ->  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 names5 (like names);
+explain (costs off) insert into names5 select * from names order by last_name returning *;
+                  QUERY PLAN                  
+----------------------------------------------
+ Insert on names5
+   ->  Gather Merge
+         Workers Planned: 3
+         ->  Sort
+               Sort Key: names.last_name
+               ->  Parallel Seq Scan on names
+(6 rows)
+
+insert into names5 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 names6 (like names);
+explain (costs off) insert into names6 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+                  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 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.
+-- (should not use a parallel plan)
+--
+create temporary table temp_names (like names);
+explain (costs off) insert into temp_names select * from names;
+       QUERY PLAN        
+-------------------------
+ Insert on temp_names
+   ->  Seq Scan on names
+(2 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+-- a: no default
+-- b: unsafe default
+-- c: restricted default
+-- d: safe default
+--
+--
+-- No column defaults, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,c,d) select a,a*2,a*4,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,c,d) select a,a*2,a*4,a*8 from test_data;
+select * from testdef order by a;
+ a  | b  | c  | d  
+----+----+----+----
+  1 |  2 |  4 |  8
+  2 |  4 |  8 | 16
+  3 |  6 | 12 | 24
+  4 |  8 | 16 | 32
+  5 | 10 | 20 | 40
+  6 | 12 | 24 | 48
+  7 | 14 | 28 | 56
+  8 | 16 | 32 | 64
+  9 | 18 | 36 | 72
+ 10 | 20 | 40 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel unsafe column default, should not use a parallel plan
+--
+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)
+
+insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+select * from testdef order by a;
+ a  | b | c  | d  
+----+---+----+----
+  1 | 5 |  4 |  8
+  2 | 5 |  8 | 16
+  3 | 5 | 12 | 24
+  4 | 5 | 16 | 32
+  5 | 5 | 20 | 40
+  6 | 5 | 24 | 48
+  7 | 5 | 28 | 56
+  8 | 5 | 32 | 64
+  9 | 5 | 36 | 72
+ 10 | 5 | 40 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel 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 safe column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,c) select a,a*2,a*4 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,c) select a,a*2,a*4 from test_data;
+select * from testdef order by a;
+ a  | b  | c  | d  
+----+----+----+----
+  1 |  2 |  4 | 20
+  2 |  4 |  8 | 20
+  3 |  6 | 12 | 20
+  4 |  8 | 16 | 20
+  5 | 10 | 20 | 20
+  6 | 12 | 24 | 20
+  7 | 14 | 28 | 20
+  8 | 16 | 32 | 20
+  9 | 18 | 36 | 20
+ 10 | 20 | 40 | 20
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel 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)
+
+insert into testdef(a,d) select a,a*8 from test_data;
+select * from testdef order by a;
+ a  | b | c  | d  
+----+---+----+----
+  1 | 5 | 10 |  8
+  2 | 5 | 10 | 16
+  3 | 5 | 10 | 24
+  4 | 5 | 10 | 32
+  5 | 5 | 10 | 40
+  6 | 5 | 10 | 48
+  7 | 5 | 10 | 56
+  8 | 5 | 10 | 64
+  9 | 5 | 10 | 72
+ 10 | 5 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- 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);
+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 INSERT into partition with parallel-unsafe partition key expression
+-- (should not create a parallel plan)
+--
+create function my_int4_sort(int4,int4) returns int language sql
+  as $$ select case when $1 = $2 then 0 when $1 > $2 then 1 else -1 end; $$;
+create operator class test_int4_ops for type int4 using btree as
+  operator 1 < (int4,int4), operator 2 <= (int4,int4),
+  operator 3 = (int4,int4), operator 4 >= (int4,int4),
+  operator 5 > (int4,int4), function 1 my_int4_sort(int4,int4);
+create table partkey_unsafe_key_expr_t (a int4, b name) partition by range (a test_int4_ops);
+create table partkey_unsafe_key_expr_t_1 partition of partkey_unsafe_key_expr_t for values from (0) to (5000);
+create table partkey_unsafe_key_expr_t_2 partition of partkey_unsafe_key_expr_t for values from (5000) to (10000);
+explain (costs off) insert into partkey_unsafe_key_expr_t select unique1, stringu1 from tenk1;
+             QUERY PLAN              
+-------------------------------------
+ Insert on partkey_unsafe_key_expr_t
+   ->  Seq Scan on tenk1
+(2 rows)
+
+--
+-- Test INSERT into table with parallel-safe check constraint
+-- (should create a parallel plan)
+--
+create or replace function check_a(a int4) returns boolean as $$
+    begin
+        return (a >= 0 and a <= 9999);
+    end;
+$$ language plpgsql parallel safe;
+create table table_check_a(a int4 check (check_a(a)), b name);
+explain (costs off) insert into table_check_a select unique1, stringu1 from tenk1;
+               QUERY PLAN               
+----------------------------------------
+ Insert on table_check_a
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into table_check_a select unique1, stringu1 from tenk1;
+select count(*), sum(a) from table_check_a;
+ count |   sum    
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT into table with parallel-unsafe check constraint
+-- (should not create a parallel plan)
+--
+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);
+explain (costs off) insert into table_check_b(a,b,c) select unique1, unique2, stringu1 from tenk1;
+       QUERY PLAN        
+-------------------------
+ Insert on table_check_b
+   ->  Seq Scan on tenk1
+(2 rows)
+
+insert into table_check_b(a,b,c) select unique1, stringu1, stringu2 from tenk1;
+select count(*), sum(a) from table_check_b;
+ count |   sum    
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT into table with before+after parallel-safe stmt-level triggers
+-- (should create a parallel SELECT plan;
+--  stmt-level before+after triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+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 or replace function insert_after_trigger_safe() returns trigger as $$
+    begin
+        raise notice 'hello from insert_after_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();
+create trigger insert_after_trigger_safe after insert on names_with_safe_trigger
+    for each statement execute procedure insert_after_trigger_safe();
+explain (costs off) insert into names_with_safe_trigger select * from names;
+               QUERY PLAN               
+----------------------------------------
+ Insert on names_with_safe_trigger
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Seq Scan on names
+(4 rows)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE:  hello from insert_before_trigger_safe
+NOTICE:  hello from insert_after_trigger_safe
+--
+-- Test INSERT into table with before+after parallel-unsafe stmt-level triggers
+-- (should not create a parallel plan;
+--  stmt-level before+after triggers should fire)
+--
+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 or replace function insert_after_trigger_unsafe() returns trigger as $$
+    begin
+        raise notice 'hello from insert_after_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();
+create trigger insert_after_trigger_unsafe after insert on names_with_unsafe_trigger
+    for each statement execute procedure insert_after_trigger_unsafe();
+explain (costs off) insert into names_with_unsafe_trigger select * from names;
+             QUERY PLAN              
+-------------------------------------
+ Insert on names_with_unsafe_trigger
+   ->  Seq Scan on names
+(2 rows)
+
+insert into names_with_unsafe_trigger select * from names;
+NOTICE:  hello from insert_before_trigger_unsafe
+NOTICE:  hello from insert_after_trigger_unsafe
+--
+-- Test INSERT into table with before+after parallel-restricted stmt-level trigger
+-- (should create a parallel plan with parallel SELECT;
+--  stmt-level before+after triggers should fire)
+--
+create table names_with_restricted_trigger (like names);
+create or replace function insert_before_trigger_restricted() returns trigger as $$
+    begin
+        raise notice 'hello from insert_before_trigger_restricted';
+		return new;
+    end;
+$$ language plpgsql parallel restricted;
+create or replace function insert_after_trigger_restricted() returns trigger as $$
+    begin
+        raise notice 'hello from insert_after_trigger_restricted';
+		return new;
+    end;
+$$ language plpgsql parallel restricted;
+create trigger insert_before_trigger_restricted before insert on names_with_restricted_trigger
+    for each statement execute procedure insert_before_trigger_restricted();
+create trigger insert_after_trigger_restricted after insert on names_with_restricted_trigger
+    for each statement execute procedure insert_after_trigger_restricted();
+explain (costs off) insert into names_with_restricted_trigger select * from names;
+               QUERY PLAN                
+-----------------------------------------
+ Insert on names_with_restricted_trigger
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Seq Scan on names
+(4 rows)
+
+insert into names_with_restricted_trigger select * from names;
+NOTICE:  hello from insert_before_trigger_restricted
+NOTICE:  hello from insert_after_trigger_restricted
+--
+-- Test INSERT into table with TOAST column
+--
+create table insert_toast_table(index int4, data text);
+create table insert_toast_table_data (like insert_toast_table);
+insert into insert_toast_table_data select i, rpad('T', 16384, 'ABCDEFGH') from generate_series(1,20) as i;
+explain (costs off) insert into insert_toast_table select index, data from insert_toast_table_data;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Insert on insert_toast_table
+   ->  Gather
+         Workers Planned: 3
+         ->  Parallel Seq Scan on insert_toast_table_data
+(4 rows)
+
+insert into insert_toast_table select index, data from insert_toast_table_data;
+select count(*) as row_count, sum(length(data)) as total_data_length from insert_toast_table;
+ row_count | total_data_length 
+-----------+-------------------
+        20 |            327680
+(1 row)
+
+--
+-- Test INSERT into table having a 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 or replace function sql_is_distinct_from_r(a anyelement, b anyelement) returns boolean as $$
+    begin
+        return (a <> b);
+    end;
+$$ language plpgsql parallel restricted;
+create or replace function sql_is_distinct_from_s(a anyelement, b anyelement) returns boolean as $$
+    begin
+        return (a <> b);
+    end;
+$$ language plpgsql parallel safe;
+create domain inotnull_u int
+  check (sql_is_distinct_from_u(value, null));
+create domain inotnull_r int
+  check (sql_is_distinct_from_r(value, null));
+create domain inotnull_s int
+  check (sql_is_distinct_from_s(value, null));
+create table dom_table_u (x inotnull_u, y int);
+create table dom_table_r (x inotnull_r, y int);
+create table dom_table_s (x inotnull_s, y int);
+-- Test INSERT into table having a DOMAIN column with parallel-unsafe CHECK constraint
+explain (costs off) insert into dom_table_u select unique1, unique2 from tenk1;
+       QUERY PLAN        
+-------------------------
+ Insert on dom_table_u
+   ->  Seq Scan on tenk1
+(2 rows)
+
+insert into dom_table_u select unique1, unique2 from tenk1;
+select count(*), sum(x) as sum_x, sum(y) as sum_y from dom_table_u;
+ count |  sum_x   |  sum_y   
+-------+----------+----------
+ 10000 | 49995000 | 49995000
+(1 row)
+
+-- Test INSERT into table having a DOMAIN column with parallel-restricted CHECK constraint
+explain (costs off) insert into dom_table_r select unique1, unique2 from tenk1;
+               QUERY PLAN               
+----------------------------------------
+ Insert on dom_table_r
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into dom_table_r select unique1, unique2 from tenk1;
+select count(*), sum(x) as sum_x, sum(y) as sum_y from dom_table_r;
+ count |  sum_x   |  sum_y   
+-------+----------+----------
+ 10000 | 49995000 | 49995000
+(1 row)
+
+-- Test INSERT into table having a DOMAIN column with parallel-safe CHECK constraint
+-- NOTE: Currently max_parallel_hazard() regards CoerceToDomain as parallel-restricted
+explain (costs off) insert into dom_table_s select unique1, unique2 from tenk1;
+               QUERY PLAN               
+----------------------------------------
+ Insert on dom_table_s
+   ->  Gather
+         Workers Planned: 4
+         ->  Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into dom_table_s select unique1, unique2 from tenk1;
+select count(*), sum(x) as sum_x, sum(y) as sum_y from dom_table_s;
+ count |  sum_x   |  sum_y   
+-------+----------+----------
+ 10000 | 49995000 | 49995000
+(1 row)
+
+rollback;
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names3_fullname_idx;
+drop table names3;
+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_safe;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0e1ef71dd..c78f213bf8 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -90,6 +90,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/serial_schedule b/src/test/regress/serial_schedule
index 081fce32e7..41ff09791d 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -148,6 +148,7 @@ test: stats_ext
 test: collate.linux.utf8
 test: select_parallel
 test: write_parallel
+test: insert_parallel
 test: publication
 test: subscription
 test: select_views
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..93b2518f79
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,531 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+-- 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_safe(f text, l text) returns text as $$
+    begin
+        return f || l;
+    end;
+$$ language plpgsql immutable parallel safe;
+
+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 names3(index int, first_name text, last_name text);
+create index names3_fullname_idx on names3 (fullname_parallel_safe(first_name, last_name));
+
+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.
+--
+
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+begin isolation level repeatable read;
+
+-- 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
+);
+
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+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;
+select * from para_insert_p1 where unique1 >= 9990 order by unique1;
+-- 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 ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+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;
+select * from para_insert_p1 where unique1 >= 9990 order by unique1;
+-- 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 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)
+--
+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;
+select * from para_insert_f1 where unique1 >= 9990 order by unique1;
+
+--
+-- Test INSERT with underlying query, leader participation disabled
+--
+set parallel_leader_participation = off;
+truncate para_insert_p1 cascade;
+explain(costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 <= 2500;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 <= 2500;
+select count(*), sum(unique1) from para_insert_p1;
+select * from para_insert_p1 where unique1 >= 2490 order by unique1;
+
+--
+-- Test INSERT with underlying query, leader participation disabled
+--  and no workers available
+set max_parallel_workers=0;
+truncate para_insert_p1 cascade;
+explain(costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 <= 2500;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 <= 2500;
+select count(*), sum(unique1) from para_insert_p1;
+select * from para_insert_p1 where unique1 >= 2490 order by unique1;
+
+reset parallel_leader_participation;
+reset max_parallel_workers;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_data2(like test_data);
+insert into test_data2 select i from generate_series(1,10000) i;
+create table test_conflict_table(id serial primary key, somedata int);
+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;
+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 parallelized aggregate
+--
+create table tenk1_avg_data(count int, avg_unique1 int, avg_stringu1_len int);
+explain (costs off) insert into tenk1_avg_data select count(*), avg(unique1), avg(length(stringu1)) from tenk1;
+insert into tenk1_avg_data select count(*), avg(unique1), avg(length(stringu1)) from tenk1;
+select * from tenk1_avg_data;
+
+--
+-- Test INSERT with parallel bitmap heap scan
+--
+set enable_seqscan to off;
+set enable_indexscan to off;
+truncate para_insert_p1 cascade;
+explain(costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 >= 7500;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 >= 7500;
+-- select some values to verify that the insert worked
+select * from para_insert_p1 where unique1 >= 9990 order by unique1;
+reset enable_seqscan;
+reset enable_indexscan;
+
+--
+-- Test INSERT with parallel append
+--
+create table a_star_data(aa int);
+explain (costs off) insert into a_star_data select aa from a_star where aa > 10;
+insert into a_star_data select aa from a_star where aa > 10;
+select count(aa), sum(aa) from a_star_data;
+
+--
+-- Test INSERT with parallel index scan
+--
+set enable_seqscan to off;
+set enable_bitmapscan to off;
+set min_parallel_index_scan_size=0;
+
+truncate para_insert_p1 cascade;
+explain(costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 >= 500;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 where unique1 >= 500;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+select * from para_insert_p1 where unique1 >= 9990 order by unique1;
+
+--
+-- Test INSERT with parallel index-only scan
+--
+truncate para_insert_p1 cascade;
+explain(costs off) insert into para_insert_p1 select unique1 from tenk1 where unique1 >= 500;
+insert into para_insert_p1 select unique1 from tenk1 where unique1 >= 500;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+select unique1 from para_insert_p1 where unique1 >= 9990 order by unique1;
+
+reset min_parallel_index_scan_size;
+reset enable_seqscan;
+reset enable_bitmapscan;
+
+--
+-- Test INSERT with parallel-safe index expression
+-- (should create a parallel plan)
+--
+explain (costs off) insert into names3 select * from names;
+insert into names3 select * from names;
+select * from names3 order by fullname_parallel_safe(first_name, last_name);
+
+--
+-- Test INSERT with parallel-unsafe index expression
+-- (should not create a parallel plan)
+--
+explain (costs off) insert into names2 select * from names;
+insert into names2 select * from names;
+select * from names2 order by fullname_parallel_unsafe(first_name, last_name);
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names4 (like names);
+explain (costs off) insert into names4 select * from names returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+explain (costs off) insert into names5 select * from names order by last_name returning *;
+insert into names5 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 names6 (like names);
+explain (costs off) insert into names6 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names6 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.
+-- (should not use a parallel plan)
+--
+create temporary table temp_names (like names);
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+-- a: no default
+-- b: unsafe default
+-- c: restricted default
+-- d: safe default
+--
+
+--
+-- No column defaults, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,c,d) select a,a*2,a*4,a*8 from test_data;
+insert into testdef(a,b,c,d) select a,a*2,a*4,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel 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 safe column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,c) select a,a*2,a*4 from test_data;
+insert into testdef(a,b,c) select a,a*2,a*4 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel 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;
+insert into testdef(a,d) select a,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- 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);
+
+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 INSERT into partition with parallel-unsafe partition key expression
+-- (should not create a parallel plan)
+--
+create function my_int4_sort(int4,int4) returns int language sql
+  as $$ select case when $1 = $2 then 0 when $1 > $2 then 1 else -1 end; $$;
+
+create operator class test_int4_ops for type int4 using btree as
+  operator 1 < (int4,int4), operator 2 <= (int4,int4),
+  operator 3 = (int4,int4), operator 4 >= (int4,int4),
+  operator 5 > (int4,int4), function 1 my_int4_sort(int4,int4);
+
+create table partkey_unsafe_key_expr_t (a int4, b name) partition by range (a test_int4_ops);
+create table partkey_unsafe_key_expr_t_1 partition of partkey_unsafe_key_expr_t for values from (0) to (5000);
+create table partkey_unsafe_key_expr_t_2 partition of partkey_unsafe_key_expr_t for values from (5000) to (10000);
+
+explain (costs off) insert into partkey_unsafe_key_expr_t select unique1, stringu1 from tenk1;
+
+--
+-- Test INSERT into table with parallel-safe check constraint
+-- (should create a parallel plan)
+--
+create or replace function check_a(a int4) returns boolean as $$
+    begin
+        return (a >= 0 and a <= 9999);
+    end;
+$$ language plpgsql parallel safe;
+
+create table table_check_a(a int4 check (check_a(a)), b name);
+explain (costs off) insert into table_check_a select unique1, stringu1 from tenk1;
+insert into table_check_a select unique1, stringu1 from tenk1;
+select count(*), sum(a) from table_check_a;
+
+--
+-- Test INSERT into table with parallel-unsafe check constraint
+-- (should not create a parallel plan)
+--
+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);
+explain (costs off) insert into table_check_b(a,b,c) select unique1, unique2, stringu1 from tenk1;
+insert into table_check_b(a,b,c) select unique1, stringu1, stringu2 from tenk1;
+select count(*), sum(a) from table_check_b;
+
+--
+-- Test INSERT into table with before+after parallel-safe stmt-level triggers
+-- (should create a parallel SELECT plan;
+--  stmt-level before+after triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+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 or replace function insert_after_trigger_safe() returns trigger as $$
+    begin
+        raise notice 'hello from insert_after_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();
+create trigger insert_after_trigger_safe after insert on names_with_safe_trigger
+    for each statement execute procedure insert_after_trigger_safe();
+explain (costs off) insert into names_with_safe_trigger select * from names;
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test INSERT into table with before+after parallel-unsafe stmt-level triggers
+-- (should not create a parallel plan;
+--  stmt-level before+after triggers should fire)
+--
+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 or replace function insert_after_trigger_unsafe() returns trigger as $$
+    begin
+        raise notice 'hello from insert_after_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();
+create trigger insert_after_trigger_unsafe after insert on names_with_unsafe_trigger
+    for each statement execute procedure insert_after_trigger_unsafe();
+explain (costs off) insert into names_with_unsafe_trigger select * from names;
+insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test INSERT into table with before+after parallel-restricted stmt-level trigger
+-- (should create a parallel plan with parallel SELECT;
+--  stmt-level before+after triggers should fire)
+--
+create table names_with_restricted_trigger (like names);
+create or replace function insert_before_trigger_restricted() returns trigger as $$
+    begin
+        raise notice 'hello from insert_before_trigger_restricted';
+		return new;
+    end;
+$$ language plpgsql parallel restricted;
+create or replace function insert_after_trigger_restricted() returns trigger as $$
+    begin
+        raise notice 'hello from insert_after_trigger_restricted';
+		return new;
+    end;
+$$ language plpgsql parallel restricted;
+create trigger insert_before_trigger_restricted before insert on names_with_restricted_trigger
+    for each statement execute procedure insert_before_trigger_restricted();
+create trigger insert_after_trigger_restricted after insert on names_with_restricted_trigger
+    for each statement execute procedure insert_after_trigger_restricted();
+explain (costs off) insert into names_with_restricted_trigger select * from names;
+insert into names_with_restricted_trigger select * from names;
+
+--
+-- Test INSERT into table with TOAST column
+--
+create table insert_toast_table(index int4, data text);
+create table insert_toast_table_data (like insert_toast_table);
+insert into insert_toast_table_data select i, rpad('T', 16384, 'ABCDEFGH') from generate_series(1,20) as i;
+explain (costs off) insert into insert_toast_table select index, data from insert_toast_table_data;
+insert into insert_toast_table select index, data from insert_toast_table_data;
+select count(*) as row_count, sum(length(data)) as total_data_length from insert_toast_table;
+
+--
+-- Test INSERT into table having a 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 or replace function sql_is_distinct_from_r(a anyelement, b anyelement) returns boolean as $$
+    begin
+        return (a <> b);
+    end;
+$$ language plpgsql parallel restricted;
+
+create or replace function sql_is_distinct_from_s(a anyelement, b anyelement) returns boolean as $$
+    begin
+        return (a <> b);
+    end;
+$$ language plpgsql parallel safe;
+
+create domain inotnull_u int
+  check (sql_is_distinct_from_u(value, null));
+
+create domain inotnull_r int
+  check (sql_is_distinct_from_r(value, null));
+
+create domain inotnull_s int
+  check (sql_is_distinct_from_s(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+create table dom_table_r (x inotnull_r, y int);
+create table dom_table_s (x inotnull_s, y int);
+
+
+-- Test INSERT into table having a DOMAIN column with parallel-unsafe CHECK constraint
+explain (costs off) insert into dom_table_u select unique1, unique2 from tenk1;
+insert into dom_table_u select unique1, unique2 from tenk1;
+select count(*), sum(x) as sum_x, sum(y) as sum_y from dom_table_u;
+
+-- Test INSERT into table having a DOMAIN column with parallel-restricted CHECK constraint
+explain (costs off) insert into dom_table_r select unique1, unique2 from tenk1;
+insert into dom_table_r select unique1, unique2 from tenk1;
+select count(*), sum(x) as sum_x, sum(y) as sum_y from dom_table_r;
+
+-- Test INSERT into table having a DOMAIN column with parallel-safe CHECK constraint
+-- NOTE: Currently max_parallel_hazard() regards CoerceToDomain as parallel-restricted
+explain (costs off) insert into dom_table_s select unique1, unique2 from tenk1;
+insert into dom_table_s select unique1, unique2 from tenk1;
+select count(*), sum(x) as sum_x, sum(y) as sum_y from dom_table_s;
+
+
+
+
+rollback;
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names3_fullname_idx;
+drop table names3;
+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_safe;
-- 
2.27.0

