From 997921a64258e1011ed4e3c3ee25afcbc05e4f88 Mon Sep 17 00:00:00 2001
From: Greg Nancarrow <gregn4422@gmail.com>
Date: Thu, 4 Mar 2021 23:29:45 +1100
Subject: [PATCH v21 1/1] 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 | 142 +++++++++---------
 src/test/regress/sql/insert_parallel.sql      |  24 +--
 3 files changed, 158 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index cec1329e25..039ac18f81 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 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.
+        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 target table is a foreign or temporary table, or 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 a2cb3056c4..f38b07489d 100644
--- a/src/test/regress/expected/insert_parallel.out
+++ b/src/test/regress/expected/insert_parallel.out
@@ -122,14 +122,14 @@ explain(costs off) insert into para_insert_p1 select unique1, stringu1 from tenk
 alter table para_insert_p1 set (parallel_dml_enabled = on);
 --
 -- 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)
 
@@ -165,7 +165,7 @@ 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)
+-- (should create plan with INSERT + parallel SELECT, GatherMerge parent node)
 --
 truncate para_insert_p1 cascade;
 NOTICE:  truncate cascades to table "para_insert_f1"
@@ -212,15 +212,15 @@ 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)
+-- (should create plan with parallel INSERT+SELECT, Gather parent node)
 --
 create table test_data1(like test_data);
 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
+ Gather
+   Workers Planned: 3
+   ->  Insert on test_data1
          ->  Parallel Seq Scan on test_data
                Filter: (a = 10)
 (5 rows)
@@ -305,9 +305,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)
@@ -344,9 +344,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)
@@ -384,9 +384,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)
 
@@ -434,9 +434,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
@@ -469,9 +469,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)
@@ -505,9 +505,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)
@@ -543,9 +543,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)
@@ -583,9 +583,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)
 
@@ -657,21 +657,21 @@ select * from names4 order by fullname_parallel_restricted(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 names5 (like names);
 explain (costs off) insert into names5 select * from names returning *;
                QUERY PLAN               
 ----------------------------------------
- Insert on names5
-   ->  Gather
-         Workers Planned: 3
+ Gather
+   Workers Planned: 3
+   ->  Insert on names5
          ->  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 names6 (like names);
 explain (costs off) insert into names6 select * from names order by last_name returning *;
@@ -700,7 +700,7 @@ 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)
+-- (should create a parallel plan; INSERT + parallel SELECT)
 --
 create table names7 (like names);
 explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
@@ -736,9 +736,9 @@ create index names8_lastname_partial_idx on names8(index, last_name) where lastn
 explain (costs off) insert into names8 select * from names;
                QUERY PLAN               
 ----------------------------------------
- Insert on names8
-   ->  Gather
-         Workers Planned: 3
+ Gather
+   Workers Planned: 3
+   ->  Insert on names8
          ->  Parallel Seq Scan on names
 (4 rows)
 
@@ -799,14 +799,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)
 
@@ -855,7 +855,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                 
@@ -884,14 +884,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)
 
@@ -967,9 +967,9 @@ alter table parttable1 set (parallel_dml_enabled = on);
 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)
 
@@ -1031,9 +1031,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)
 
@@ -1070,7 +1070,7 @@ select count(*), sum(a) from table_check_b;
 
 --
 -- Test INSERT into table with parallel-safe before+after 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);
@@ -1091,11 +1091,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)
 
@@ -1136,7 +1136,7 @@ NOTICE:  hello from insert_before_trigger_unsafe
 NOTICE:  hello from insert_after_trigger_unsafe
 --
 -- Test INSERT into table with parallel-restricted before+after 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);
@@ -1193,13 +1193,13 @@ create table rp1 partition of rp for values from (minvalue) to (0);
 create table rp2 partition of rp for values from (0) to (maxvalue);
 create table foo (a) as select unique1 from tenk1;
 prepare q as insert into rp select * from foo where a%2 = 0;
--- should create a parallel plan
+-- should create a plan with parallel INSERT+SELECT
 explain (costs off) execute q;
               QUERY PLAN              
 --------------------------------------
- Insert on rp
-   ->  Gather
-         Workers Planned: 4
+ Gather
+   Workers Planned: 4
+   ->  Insert on rp
          ->  Parallel Seq Scan on foo
                Filter: ((a % 2) = 0)
 (5 rows)
@@ -1226,9 +1226,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 ea5a1d9525..23a2f1eed6 100644
--- a/src/test/regress/sql/insert_parallel.sql
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -136,7 +136,7 @@ alter table para_insert_p1 set (parallel_dml_enabled = on);
 
 --
 -- 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;
@@ -148,7 +148,7 @@ 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)
+-- (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;
@@ -161,7 +161,7 @@ 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)
+-- (should create plan with parallel INSERT+SELECT, Gather parent node)
 --
 create table test_data1(like test_data);
 explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
@@ -303,14 +303,14 @@ select * from names4 order by fullname_parallel_restricted(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 names5 (like names);
 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)
+-- (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 *;
@@ -318,7 +318,7 @@ 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)
+-- (should create a parallel plan; INSERT + parallel SELECT)
 --
 create table names7 (like names);
 explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
@@ -371,7 +371,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;
@@ -387,7 +387,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;
@@ -395,7 +395,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;
@@ -494,7 +494,7 @@ select count(*), sum(a) from table_check_b;
 
 --
 -- Test INSERT into table with parallel-safe before+after 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);
@@ -544,7 +544,7 @@ insert into names_with_unsafe_trigger select * from names;
 
 --
 -- Test INSERT into table with parallel-restricted before+after 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);
@@ -590,7 +590,7 @@ create table rp1 partition of rp for values from (minvalue) to (0);
 create table rp2 partition of rp for values from (0) to (maxvalue);
 create table foo (a) as select unique1 from tenk1;
 prepare q as insert into rp select * from foo where a%2 = 0;
--- should create a parallel plan
+-- should create a plan with parallel INSERT+SELECT
 explain (costs off) execute q;
 
 create or replace function make_table_bar () returns trigger language
-- 
2.27.0

