From 2937b6d1f6a50d285b6f7615b760f007bbb52ac2 Mon Sep 17 00:00:00 2001 From: Greg Nancarrow Date: Thu, 19 Nov 2020 13:06:16 +1100 Subject: [PATCH v8 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 | 1063 +++++++++++++++++++++++++ src/test/regress/parallel_schedule | 1 + src/test/regress/serial_schedule | 1 + src/test/regress/sql/insert_parallel.sql | 526 ++++++++++++ 5 files changed, 1594 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 c81abff..938d51a 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 CREATE TABLE ... AS, SELECT INTO, and CREATE MATERIALIZED VIEW which create a new - table and populate it can use a parallel plan. + table and populate it can use a parallel plan. Another exeption is the command + INSERT INTO ... SELECT ... which can use a parallel plan for + the underlying SELECT part of the query. diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out new file mode 100644 index 0000000..5f0a89f --- /dev/null +++ b/src/test/regress/expected/insert_parallel.out @@ -0,0 +1,1063 @@ +-- +-- 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 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) + +-- +-- 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) + +-- +-- 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 ae89ed7..4fa4b97 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -88,6 +88,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 525bdc8..261cab7 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -147,6 +147,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 0000000..9447120 --- /dev/null +++ b/src/test/regress/sql/insert_parallel.sql @@ -0,0 +1,526 @@ +-- +-- 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 count(*), sum(unique1) from para_insert_p1; +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) +-- +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; + +-- +-- 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; -- 1.8.3.1