From e43a71f49ea3cb3ab88c0bb7dbcd3d126e72f531 Mon Sep 17 00:00:00 2001
From: Greg Nancarrow <gregn4422@gmail.com>
Date: Fri, 20 Nov 2020 17:46:01 +1100
Subject: [PATCH v8 4/4] Parallel INSERT and/or SELECT for "INSERT INTO ...
 SELECT ..." - tests and documentation updates.

---
 doc/src/sgml/parallel.sgml                    |  83 ++++++++++++++++--
 src/test/regress/expected/insert_parallel.out | 120 +++++++++++++-------------
 src/test/regress/sql/insert_parallel.sql      |  20 ++---
 3 files changed, 145 insertions(+), 78 deletions(-)

diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index 938d51a..3cc029c 100644
--- a/doc/src/sgml/parallel.sgml
+++ b/doc/src/sgml/parallel.sgml
@@ -141,14 +141,16 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
   <itemizedlist>
     <listitem>
       <para>
-        The query writes any data or locks any database rows. If a query
-        contains a data-modifying operation either at the top level or within
-        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. Another exeption is the command
-        <literal>INSERT INTO ... SELECT ...</literal> which can use a parallel plan for
-        the underlying <literal>SELECT</literal> part of the query.
+        The query locks any database rows, or writes data within a CTE or using
+        a parallel-unsupported data-modifying operation. Currently, the only
+        parallel-supported data-modifying operations are
+        <literal>INSERT INTO ... SELECT ...</literal>, and the table creation
+        and population commands <literal>CREATE TABLE ... AS</literal>,
+        <literal>SELECT INTO</literal>, and
+        <literal>CREATE MATERIALIZED VIEW</literal>. If a query contains a
+        parallel-unsupported data-modifying operation at the top level, or any
+        data-modifying operation within a CTE, no parallel plans for that query
+        will be generated.
       </para>
     </listitem>
 
@@ -426,6 +428,71 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
   </para>
  </sect2>
 
+ <sect2 id="parallel-insert">
+  <title>Parallel Insert</title>
+
+  <para>
+    When an <literal>INSERT</literal> statement uses an underlying
+    <literal>SELECT</literal> query to supply the rows to be inserted, a
+    parallel query plan may be generated for which the work of both data
+    retrieval and data insertion is divided amongst the workers. In this
+    case, each worker handles insertion of its portion of the rows retrieved
+    by the underlying <literal>SELECT</literal> query.
+  </para>
+
+  <para>
+    Parallel <literal>INSERT</literal> is not supported in all situations. The
+    operations invoked by the <literal>INSERT</literal> statement must be
+    parallel-safe, including those that are invoked as a result of certain
+    features of the target table.
+  </para>
+
+  <para>
+    For example, any of the following will prevent the use of parallel
+    <literal>INSERT</literal> in the query plan:
+  </para>
+
+  <itemizedlist>
+    <listitem>
+      <para>
+        A parallel query plan can't be generated for the underlying
+        <literal>SELECT</literal>, because, for example, the
+        <literal>SELECT</literal> statement uses a parallel-unsafe function.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        The INSERT statement uses an ON CONFLICT DO UPDATE clause.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        The target table is a foreign or temporary table.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        The target table has a foreign key, or has a parallel-unsafe trigger,
+        index expression, column default expression or check constraint.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        The target table is a partitioned table with a parallel-unsafe partition
+        key expression or support function.
+      </para>
+    </listitem>
+  </itemizedlist>
+
+  <para>
+    Where the above target table features are determined to be, at worst,
+    parallel-restricted, rather than parallel-unsafe, at least a parallel table
+    scan may be used in the query plan for the <literal>INSERT</literal>
+    statement. For more information about Parallel Safety, see
+    <xref linkend="parallel-safety"/>.
+  </para>
+ </sect2>
+
  <sect2 id="parallel-plan-tips">
   <title>Parallel Plan Tips</title>
 
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
index 5f0a89f..5755fc1 100644
--- a/src/test/regress/expected/insert_parallel.out
+++ b/src/test/regress/expected/insert_parallel.out
@@ -70,14 +70,14 @@ create table para_insert_f1 (
 );
 --
 -- Test INSERT with underlying query.
--- (should create plan with parallel SELECT, Gather parent node)
+-- (should create plan with parallel INSERT+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
+ Gather
+   Workers Planned: 4
+   ->  Insert on para_insert_p1
          ->  Parallel Seq Scan on tenk1
 (4 rows)
 
@@ -105,7 +105,7 @@ select * from para_insert_p1 where unique1 >= 9990 order by unique1;
 
 --
 -- Test INSERT with ordered underlying query.
--- (should create plan with parallel SELECT, GatherMerge parent node)
+-- (should create plan with INSERT + parallel SELECT, GatherMerge parent node)
 --
 truncate para_insert_p1 cascade;
 NOTICE:  truncate cascades to table "para_insert_f1"
@@ -190,9 +190,9 @@ 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
+ Gather
+   Workers Planned: 4
+   ->  Insert on para_insert_p1
          ->  Parallel Seq Scan on tenk1
                Filter: (unique1 <= 2500)
 (5 rows)
@@ -229,9 +229,9 @@ 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
+ Gather
+   Workers Planned: 4
+   ->  Insert on para_insert_p1
          ->  Parallel Seq Scan on tenk1
                Filter: (unique1 <= 2500)
 (5 rows)
@@ -271,9 +271,9 @@ 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
+ Gather
+   Workers Planned: 3
+   ->  Insert on test_conflict_table
          ->  Parallel Seq Scan on test_data
 (4 rows)
 
@@ -321,9 +321,9 @@ 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
+ Gather
+   Workers Planned: 4
+   ->  Insert on para_insert_p1
          ->  Parallel Bitmap Heap Scan on tenk1
                Recheck Cond: (unique1 >= 7500)
                ->  Bitmap Index Scan on tenk1_unique1
@@ -356,9 +356,9 @@ 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
+ Gather
+   Workers Planned: 3
+   ->  Insert on a_star_data
          ->  Parallel Append
                ->  Parallel Seq Scan on d_star a_star_4
                      Filter: (aa > 10)
@@ -392,9 +392,9 @@ 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
+ Gather
+   Workers Planned: 4
+   ->  Insert on para_insert_p1
          ->  Parallel Index Scan using tenk1_unique1 on tenk1
                Index Cond: (unique1 >= 500)
 (5 rows)
@@ -430,9 +430,9 @@ 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
+ Gather
+   Workers Planned: 4
+   ->  Insert on para_insert_p1
          ->  Parallel Index Only Scan using tenk1_unique1 on tenk1
                Index Cond: (unique1 >= 500)
 (5 rows)
@@ -470,9 +470,9 @@ reset enable_bitmapscan;
 explain (costs off) insert into names3 select * from names;
                QUERY PLAN               
 ----------------------------------------
- Insert on names3
-   ->  Gather
-         Workers Planned: 3
+ Gather
+   Workers Planned: 3
+   ->  Insert on names3
          ->  Parallel Seq Scan on names
 (4 rows)
 
@@ -517,21 +517,21 @@ 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)
+-- (should create a parallel plan; parallel INSERT+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
+ Gather
+   Workers Planned: 3
+   ->  Insert on names4
          ->  Parallel Seq Scan on names
 (4 rows)
 
 --
 -- Test INSERT with underlying ordered query - and RETURNING (no projection)
--- (should create a parallel plan; parallel SELECT)
+-- (should create a parallel plan; INSERT + parallel SELECT)
 --
 create table names5 (like names);
 explain (costs off) insert into names5 select * from names order by last_name returning *;
@@ -560,7 +560,7 @@ 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)
+-- (should create a parallel plan; INSERT + 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;
@@ -610,14 +610,14 @@ insert into temp_names select * from names;
 -- d: safe default
 --
 --
--- No column defaults, should use parallel SELECT
+-- No column defaults, should use parallel INSERT+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
+ Gather
+   Workers Planned: 3
+   ->  Insert on testdef
          ->  Parallel Seq Scan on test_data
 (4 rows)
 
@@ -666,7 +666,7 @@ select * from testdef order by a;
 
 truncate testdef;
 --
--- Parallel restricted column default, should use parallel SELECT
+-- Parallel restricted column default, should use INSERT + parallel SELECT
 --
 explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
                  QUERY PLAN                 
@@ -695,14 +695,14 @@ select * from testdef order by a;
 
 truncate testdef;
 --
--- Parallel safe column default, should use parallel SELECT
+-- Parallel safe column default, should use parallel INSERT+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
+ Gather
+   Workers Planned: 3
+   ->  Insert on testdef
          ->  Parallel Seq Scan on test_data
 (4 rows)
 
@@ -759,9 +759,9 @@ create table parttable1_2 partition of parttable1 for values from (5000) to (100
 explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
                QUERY PLAN               
 ----------------------------------------
- Insert on parttable1
-   ->  Gather
-         Workers Planned: 4
+ Gather
+   Workers Planned: 4
+   ->  Insert on parttable1
          ->  Parallel Seq Scan on tenk1
 (4 rows)
 
@@ -811,9 +811,9 @@ 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
+ Gather
+   Workers Planned: 4
+   ->  Insert on table_check_a
          ->  Parallel Seq Scan on tenk1
 (4 rows)
 
@@ -850,7 +850,7 @@ 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;
+-- (should create a parallel INSERT+SELECT plan;
 --  stmt-level before+after triggers should fire)
 --
 create table names_with_safe_trigger (like names);
@@ -871,11 +871,11 @@ create trigger insert_before_trigger_safe before insert on names_with_safe_trigg
 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
+               QUERY PLAN                
+-----------------------------------------
+ Gather
+   Workers Planned: 3
+   ->  Insert on names_with_safe_trigger
          ->  Parallel Seq Scan on names
 (4 rows)
 
@@ -916,7 +916,7 @@ 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;
+-- (should create a parallel plan with INSERT + parallel SELECT;
 --  stmt-level before+after triggers should fire)
 --
 create table names_with_restricted_trigger (like names);
@@ -957,9 +957,9 @@ insert into insert_toast_table_data select i, rpad('T', 16384, 'ABCDEFGH') from
 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
+ Gather
+   Workers Planned: 3
+   ->  Insert on insert_toast_table
          ->  Parallel Seq Scan on insert_toast_table_data
 (4 rows)
 
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
index 9447120..ab231cb 100644
--- a/src/test/regress/sql/insert_parallel.sql
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -89,7 +89,7 @@ create table para_insert_f1 (
 
 --
 -- Test INSERT with underlying query.
--- (should create plan with parallel SELECT, Gather parent node)
+-- (should create plan with parallel INSERT+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;
@@ -98,7 +98,7 @@ select * from para_insert_p1 where unique1 >= 9990 order by unique1;
 
 --
 -- Test INSERT with ordered underlying query.
--- (should create plan with parallel SELECT, GatherMerge parent node)
+-- (should create plan with INSERT + 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;
@@ -229,14 +229,14 @@ 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)
+-- (should create a parallel plan; parallel INSERT+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)
+-- (should create a parallel plan; INSERT + parallel SELECT)
 --
 create table names5 (like names);
 explain (costs off) insert into names5 select * from names order by last_name returning *;
@@ -244,7 +244,7 @@ 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)
+-- (should create a parallel plan; INSERT + 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;
@@ -269,7 +269,7 @@ insert into temp_names select * from names;
 --
 
 --
--- No column defaults, should use parallel SELECT
+-- No column defaults, should use parallel INSERT+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;
@@ -285,7 +285,7 @@ select * from testdef order by a;
 truncate testdef;
 
 --
--- Parallel restricted column default, should use parallel SELECT
+-- Parallel restricted column default, should use INSERT + 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;
@@ -293,7 +293,7 @@ select * from testdef order by a;
 truncate testdef;
 
 --
--- Parallel safe column default, should use parallel SELECT
+-- Parallel safe column default, should use parallel INSERT+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;
@@ -370,7 +370,7 @@ 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;
+-- (should create a parallel INSERT+SELECT plan;
 --  stmt-level before+after triggers should fire)
 --
 create table names_with_safe_trigger (like names);
@@ -420,7 +420,7 @@ 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;
+-- (should create a parallel plan with INSERT + parallel SELECT;
 --  stmt-level before+after triggers should fire)
 --
 create table names_with_restricted_trigger (like names);
-- 
1.8.3.1

