Parallel INSERT SELECT take 2
This is another try of [1]Parallel INSERT (INTO ... SELECT ...) /messages/by-id/CAJcOf-cXnB5cnMKqWEp2E2z7Mvcd04iLVmV=qpFJrR3AcrTS3g@mail.gmail.com.
BACKGROUND
========================================
We want to realize parallel INSERT SELECT in the following steps:
1) INSERT + parallel SELECT
2) Parallel INSERT + parallel SELECT
Below are example use cases. We don't expect high concurrency or an empty data source.
* Data loading (ETL or ELT) into an analytics database, typically a data ware house.
* Batch processing in an OLTP database.
PROBLEMS
========================================
(1) The overhead of checking parallel-safety could be large
We have to check the target table and its child partitions for parallel safety. That is, we make sure those relations don't have parallel-unsafe domains, constraints, indexes, or triggers.
What we should check is the relations into which the statement actually inserts data. However, the planner does not know which relations will be actually inserted into. So, the planner has to check all descendant partitions of a target table. When the target table has many partitions, this overhead could be unacceptable when compared to the benefit gained from parallelism.
(2) There's no mechanism for parallel workers to assign an XID
Parallel workers need an XID of the current (sub)transaction when actually inserting a tuple (i.e., calling heap_insert()). When the leader has not got the XID yet, the worker may have to assign a new XID and communicate it to the leader and other workers so that all parallel processes use the same XID.
SOLUTION TO (1)
========================================
The candidate ideas are:
1) Caching the result of parallel-safety check
The planner stores the result of checking parallel safety for each relation in relcache, or some purpose-built hash table in shared memory.
The problems are:
* Even if the target relation turns out to be parallel safe by looking at those data structures, we cannot assume it remains true until the SQL statement finishes. For instance, other sessions might add a parallel-unsafe index to its descendant relations. Other examples include that when the user changes the parallel safety of indexes or triggers by running ALTER FUNCTION on the underlying index AM function or trigger function, the relcache entry of the table or index is not invalidated, so the correct parallel safety is not maintained in the cache.
In that case, when the executor encounters a parallel-unsafe object, it can change the cached state as being parallel-unsafe and error out.
* Can't ensure fast access. With relcache, the first access in each session has to undergo the overhead of parallel-safety check. With a hash table in shared memory, the number of relations stored there would be limited, so the first access after database startup or the hash table entry eviction similarly experiences slowness.
* With a new hash table, some lwlock for concurrent access must be added, which can have an adverse effect on performance.
2) Enabling users to declare that the table allows parallel data modification
Add a table property that represents parallel safety of the table for DML statement execution. Users specify it as follows:
CREATE TABLE table_name (...) PARALLEL { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u', 'r', or 's', just like pg_proc's proparallel. The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions, and their ancillary objects have the specified parallel safety or safer one. The user is responsible for its correctness. If the parallel processes find an object that is less safer than the assumed parallel safety during statement execution, it throws an ERROR and abort the statement execution.
The objects that relate to the parallel safety of a DML target table are as follows:
* Column default expression
* DOMAIN type CHECK expression
* CHECK constraints on column
* Partition key
* Partition key support function
* Index expression
* Index predicate
* Index AM function
* Operator function
* Trigger function
When the parallel safety of some of these objects is changed, it's costly to reflect it on the parallel safety of tables that depend on them. So, we don't do it. Instead, we provide a utility function pg_get_parallel_safety('table_name') that returns records of (objid, classid, parallel_safety) that represent the parallel safety of objects that determine the parallel safety of the specified table. The function only outputs objects that are not parallel safe. Otherwise, it will consume excessive memory while accumulating the output. The user can use this function to identify problematic objects when a parallel DML fails or is not parallelized in an expected manner.
How does the executor detect parallel unsafe objects? There are two ways:
1) At loading time
When the executor loads the definition of objects (tables, constraints, index, triggers, etc.) during the first access to them after session start or their eviction by sinval message, it checks the parallel safety.
This is a legitimate way, but may need much code. Also, it might overlook necessary code changes without careful inspection.
2) At function execution time
All related objects come down to some function execution. So, add a parallel safety check there when in a parallel worker. If the current process is a parallel worker and the function is parallel unsafe, error out with ereport(ERROR). This approach eliminates the oversight of parallel safety check with the additional bonus of tiny code change!
The place would be FunctionCallInvoke(). It's a macro in fmgr.h now. Perhaps we should make it a function in fmgr.c, so that fmgr.h does not have to include header files for parallelism-related definitions.
We have to evaluate the performance effect of converting FunctionCallInvoke() into a function and adding an if statement there, because it's a relatively low-level function.
SOLUTION TO (2)
========================================
1) Make it possible for workers to assign an XID and share it among the parallel processes
The problems are:
* Tuple visibility
If the worker that acquires the XID writes some row and another worker reads that row before it gets to see the XID information, the latter worker won't treat such a row is written by its own transaction.
For instance, the worker (w-1) that acquires the XID (501) deletes the tuple (CTID: 0, 2). Now, another worker (w-2) reads that tuple (CTID: 0, 2), it would consider that the tuple is still visible to its snapshot but if the w-2 knows that 501 is its own XID, it would have been considered it as (not-visible) deleted. I think this can happen when multiple updates to the same row happen and new rows get added to the new page.
* The implementation seems complex
When the DML is run inside a deeply nested subtransaction and the parent transactions have not allocated their XIDs yet, the worker needs to allocate the XIDs for its parents. That indeterminate number of XIDs must be stored in shared memory. The stack of TransactionState structures must also be passed.
Also, TransactionIdIsCurrentTransactionId() uses an array ParallelCurrentXids where parallel workers receive sub-committed XIDs from the leader. This needs to be reconsidered.
2) The leader assigns an XID before entering parallel mode and passes it to workers
This is what was done in [1]Parallel INSERT (INTO ... SELECT ...) /messages/by-id/CAJcOf-cXnB5cnMKqWEp2E2z7Mvcd04iLVmV=qpFJrR3AcrTS3g@mail.gmail.com.
The problem is that the XID would not be used if the data source (SELECT query) returns no valid rows. This is a waste of XID.
However, the data source should be rarely empty when this feature is used. As the following Oracle manual says, parallel DML will be used in data analytics and OLTP batch jobs. There should be plenty of source data in those scenarios.
When to Use Parallel DML
https://docs.oracle.com/en/database/oracle/oracle-database/21/vldbg/types-parallelism.html#GUID-18B2AF09-C548-48DE-A794-86224111549F
--------------------------------------------------
Several scenarios where parallel DML is used include:
Refreshing Tables in a Data Warehouse System
Creating Intermediate Summary Tables
Using Scoring Tables
Updating Historical Tables
Running Batch Jobs
--------------------------------------------------
CONCLUSION
========================================
(1) The overhead of checking parallel-safety could be large
We're inclined to go with solution 2, because it doesn't have a big problem. However, we'd like to try to present some more analysis on solution 1 in this thread.
Regarding how to check parallel safety in executor, I prefer the simpler way of adding a check in function execution. If it turns out to have an untolerable performance problem, we can choose the other approach.
(2) There's no mechanism for parallel workers to assign an XID
We'd like to adopt solution 2 because it will really not have a big issue in the assumed use cases. The implementation is very easy and does not look strange.
Of course, any better-looking idea would be much appreciated. (But simple, or not unnecessarily complex, one is desired.)
[1]: Parallel INSERT (INTO ... SELECT ...) /messages/by-id/CAJcOf-cXnB5cnMKqWEp2E2z7Mvcd04iLVmV=qpFJrR3AcrTS3g@mail.gmail.com
Parallel INSERT (INTO ... SELECT ...)
/messages/by-id/CAJcOf-cXnB5cnMKqWEp2E2z7Mvcd04iLVmV=qpFJrR3AcrTS3g@mail.gmail.com
Regards
Takayuki Tsunakawa
BACKGROUND
========================================We want to realize parallel INSERT SELECT in the following steps:
1) INSERT + parallel SELECT
2) Parallel INSERT + parallel SELECTBelow are example use cases. We don't expect high concurrency or an empty
data source.
* Data loading (ETL or ELT) into an analytics database, typically a data ware
house.
* Batch processing in an OLTP database.
2) Enabling users to declare that the table allows parallel data modification Add
a table property that represents parallel safety of the table for DML statement
execution. Users specify it as follows:CREATE TABLE table_name (...) PARALLEL { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL { UNSAFE | RESTRICTED | SAFE };This property is recorded in pg_class's relparallel column as 'u', 'r', or 's', just
like pg_proc's proparallel. The default is UNSAFE.The planner assumes that all of the table, its descendant partitions, and their
ancillary objects have the specified parallel safety or safer one. The user is
responsible for its correctness. If the parallel processes find an object that is
less safer than the assumed parallel safety during statement execution, it
throws an ERROR and abort the statement execution.When the parallel safety of some of these objects is changed, it's costly to
reflect it on the parallel safety of tables that depend on them. So, we don't do
it. Instead, we provide a utility function pg_get_parallel_safety('table_name')
that returns records of (objid, classid, parallel_safety) that represent the
parallel safety of objects that determine the parallel safety of the specified
table. The function only outputs objects that are not parallel safe. Otherwise,
it will consume excessive memory while accumulating the output. The user
can use this function to identify problematic objects when a parallel DML fails
or is not parallelized in an expected manner.How does the executor detect parallel unsafe objects? There are two ways:
1) At loading time
...
2) At function execution time
All related objects come down to some function execution. So, add a parallel
safety check there when in a parallel worker. If the current process is a parallel
worker and the function is parallel unsafe, error out with ereport(ERROR). This
approach eliminates the oversight of parallel safety check with the additional
bonus of tiny code change!The place would be FunctionCallInvoke(). It's a macro in fmgr.h now. Perhaps
we should make it a function in fmgr.c, so that fmgr.h does not have to include
header files for parallelism-related definitions.We have to evaluate the performance effect of converting FunctionCallInvoke()
into a function and adding an if statement there, because it's a relatively
low-level function.
Based on above, we plan to move forward with the apporache 2) (declarative idea).
Attatching the POC patchset which including the following:
0001: provide a utility function pg_get_parallel_safety('table_name').
The function returns records of (objid, classid, parallel_safety) that represent
the parallel safety of objects that determine the parallel safety of the specified table.
Note: The function only outputs objects that are not parallel safe.
(Thanks a lot for greg's previous work, most of the safety check code here is based on it)
0002: allow user use "ALTER TABLE PARALLEL SAFE/UNSAFE/RESTRICTED".
Add proparallel column in pg_class and allow use to change its.
0003: detect parallel unsafe objects in executor.
Currently we choose to check function's parallel safety at function execution time.
We add safety check at FunctionCallInvoke(), but it may be better to check in fmgr_info_cxt_security.
we are still discussing it in another thread[1].
TODO: we currently skip checking built-in function's parallel safety, because we lack the information about built-in
function's parallel safety, we cannot access pg_proc.proparallel in a low level because it could result in infinite recursion.
Adding parallel property in fmgrBuiltin will enlarge the frequently accessed fmgr_builtins and lock down the value of the
parallel-safety flag. The solution is still under discussion. Suggestions and comments are welcome.
0004: fix some mislabeled function in testcase
Since we check parallel safety of function at a low level, we found some functions marked as parallel unsafe will be
executed in parallel mode in regression test when setting force_parallel_mode=regress. After checking, these functions
are parallel safe, So , we plan to fix these function's parallel label.
Note: we plan to take 0004 as a separate patch , see[2], I post 0004 here just to prevent some testcase failures.
The above are the POC patches, it could be imperfect for now and I am still working on improving it.
Suggestions and comments about the design or code are very welcome and appreciated.
Best regards,
houzj
Attachments:
v1-POC-0002-ALTER-TABLE-PARALLEL.patchapplication/octet-stream; name=v1-POC-0002-ALTER-TABLE-PARALLEL.patchDownload
From 5009cfb4b36a4d856ea944143ca39983bbd013b5 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 21 Apr 2021 14:31:46 +0800
Subject: [PATCH 2/3] ALTER-TABLE-PARALLEL
Enabling users to declare that the table allows parallel data modification,
Add a table property that represents parallel safety of the table for DML statement execution.
Users specify it as follows:
ALTER TABLE table_name PARALLEL { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u', 'r', or 's', just like pg_proc's proparallel. The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions, and their ancillary objects
have the specified parallel safety or safer one. The user is responsible for its correctness.
If the parallel processes find an object that is less safer than the assumed parallel safety during
statement execution, it throws an ERROR and abort the statement execution.
---
src/backend/catalog/heap.c | 1 +
src/backend/commands/tablecmds.c | 50 +++++++++++++++++++
src/backend/optimizer/util/clauses.c | 25 +++++++++-
src/backend/parser/gram.y | 8 +++
src/backend/utils/cache/relcache.c | 3 ++
src/bin/pg_dump/pg_dump.c | 47 +++++++++++++----
src/bin/pg_dump/pg_dump.h | 1 +
src/include/catalog/pg_class.h | 3 ++
src/include/nodes/parsenodes.h | 3 +-
.../test_ddl_deparse/test_ddl_deparse.c | 3 ++
10 files changed, 132 insertions(+), 12 deletions(-)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ba03e8aa8f..55ac4719aa 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -960,6 +960,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relproparallel - 1] = CharGetDatum(rd_rel->relproparallel);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 096a6f2891..66e7852ba7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static void ATExecParallelSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -4206,6 +4208,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4748,6 +4751,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5155,6 +5163,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
Assert(rel->rd_rel->relkind == RELKIND_INDEX);
ATExecAlterCollationRefreshVersion(rel, cmd->object);
break;
+ case AT_ParallelSafety:
+ ATExecParallelSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18504,3 +18515,42 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char proparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal((Value *) def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ proparallel = PROPARALLEL_SAFE;
+ else if (strcmp(parallel, "restricted") == 0)
+ proparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ proparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relproparallel = proparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 0acc0e55f4..3f47bbb664 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -177,7 +177,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
-
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -645,6 +645,7 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
@@ -654,7 +655,27 @@ max_parallel_hazard(Query *parse)
context.func_oids = NIL;
context.partition_directory = NULL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relproparallel,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 73494002ad..27a76d68c5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2690,6 +2690,14 @@ alter_table_cmd:
n->object = $3;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL SAFE */
+ | PARALLEL ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelSafety;
+ n->def = (Node *)makeString($2);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 29702d6eab..45faf38b1d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1868,6 +1868,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relproparallel = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3487,6 +3488,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relproparallel = PROPARALLEL_UNSAFE;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e397b76356..ea743261c4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6291,6 +6291,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relproparallel;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6395,7 +6396,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relproparallel, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6487,7 +6488,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relproparallel, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6540,7 +6541,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relproparallel, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6593,7 +6594,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relproparallel, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6646,7 +6647,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relproparallel, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6697,7 +6698,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relproparallel, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6745,7 +6746,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relproparallel, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6793,7 +6794,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relproparallel, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6840,7 +6841,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relproparallel, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6909,6 +6910,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relproparallel = PQfnumber(res, "relproparallel");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6964,6 +6966,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relproparallel = *(PQgetvalue(res, i, i_relproparallel));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16542,6 +16545,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relproparallel)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5340843081..517795ad14 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relproparallel; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729436..1ad816f4ae 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relproparallel BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7a44bccdd3..e5652911d2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1934,7 +1934,8 @@ typedef enum AlterTableType
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelSafety /* PARALLEL SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5438..25ba3afc6f 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelSafety:
+ strtype = "PARALLEL SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.18.4
v1-POC-0003-check-parallel-safety-in-fmgr.patchapplication/octet-stream; name=v1-POC-0003-check-parallel-safety-in-fmgr.patchDownload
From b2d74947ffc762061e2335c802510f57d1af9a82 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 21 Apr 2021 17:12:24 +0800
Subject: [PATCH 3/3] check-parallel-safety-in-fmgr
---
src/backend/access/transam/xact.c | 26 +
src/backend/executor/execExprInterp.c | 26 +-
src/backend/executor/execMain.c | 3 +
src/backend/optimizer/plan/planner.c | 18 +-
src/backend/utils/fmgr/fmgr.c | 28 +
src/include/access/xact.h | 1 +
src/include/fmgr.h | 5 +-
src/test/regress/expected/insert_parallel.out | 530 ++++++++++++++++++
src/test/regress/parallel_schedule | 1 +
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 337 +++++++++++
11 files changed, 952 insertions(+), 24 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 441445927e..2d68e4633a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1014,6 +1014,32 @@ IsInParallelMode(void)
return CurrentTransactionState->parallelModeLevel != 0;
}
+/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
/*
* CommandCounterIncrement
*/
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 094e22d392..c9e43ada7a 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -717,7 +717,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
Datum d;
fcinfo->isnull = false;
- d = op->d.func.fn_addr(fcinfo);
+ d = FuncExprCallInvoke(op->d.func.fn_addr, fcinfo);
*op->resvalue = d;
*op->resnull = fcinfo->isnull;
@@ -741,7 +741,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
}
}
fcinfo->isnull = false;
- d = op->d.func.fn_addr(fcinfo);
+ d = FuncExprCallInvoke(op->d.func.fn_addr, fcinfo);
*op->resvalue = d;
*op->resnull = fcinfo->isnull;
@@ -1223,7 +1223,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
Datum eqresult;
fcinfo->isnull = false;
- eqresult = op->d.func.fn_addr(fcinfo);
+ eqresult = FuncExprCallInvoke(op->d.func.fn_addr, fcinfo);
/* Must invert result of "="; safe to do even if null */
*op->resvalue = BoolGetDatum(!DatumGetBool(eqresult));
*op->resnull = fcinfo->isnull;
@@ -1252,7 +1252,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
Datum eqresult;
fcinfo->isnull = false;
- eqresult = op->d.func.fn_addr(fcinfo);
+ eqresult = FuncExprCallInvoke(op->d.func.fn_addr, fcinfo);
*op->resvalue = eqresult;
*op->resnull = fcinfo->isnull;
}
@@ -1273,7 +1273,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
Datum result;
fcinfo->isnull = false;
- result = op->d.func.fn_addr(fcinfo);
+ result = FuncExprCallInvoke(op->d.func.fn_addr, fcinfo);
/* if the arguments are equal return null */
if (!fcinfo->isnull && DatumGetBool(result))
@@ -1361,7 +1361,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
/* Apply comparison function */
fcinfo->isnull = false;
- d = op->d.rowcompare_step.fn_addr(fcinfo);
+ d = FuncExprCallInvoke(op->d.rowcompare_step.fn_addr, fcinfo);
*op->resvalue = d;
/* force NULL result if NULL function result */
@@ -2152,7 +2152,7 @@ ExecJustApplyFuncToCase(ExprState *state, ExprContext *econtext, bool *isnull)
}
}
fcinfo->isnull = false;
- d = op->d.func.fn_addr(fcinfo);
+ d = FuncExprCallInvoke(op->d.func.fn_addr, fcinfo);
*isnull = fcinfo->isnull;
return d;
}
@@ -2347,7 +2347,7 @@ ExecEvalFuncExprFusage(ExprState *state, ExprEvalStep *op,
pgstat_init_function_usage(fcinfo, &fcusage);
fcinfo->isnull = false;
- d = op->d.func.fn_addr(fcinfo);
+ d = FuncExprCallInvoke(op->d.func.fn_addr, fcinfo);
*op->resvalue = d;
*op->resnull = fcinfo->isnull;
@@ -2381,7 +2381,7 @@ ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op,
pgstat_init_function_usage(fcinfo, &fcusage);
fcinfo->isnull = false;
- d = op->d.func.fn_addr(fcinfo);
+ d = FuncExprCallInvoke(op->d.func.fn_addr, fcinfo);
*op->resvalue = d;
*op->resnull = fcinfo->isnull;
@@ -3379,7 +3379,7 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
else
{
fcinfo->isnull = false;
- thisresult = op->d.scalararrayop.fn_addr(fcinfo);
+ thisresult = FuncExprCallInvoke(op->d.scalararrayop.fn_addr, fcinfo);
}
/* Combine results per OR or AND semantics */
@@ -3436,7 +3436,7 @@ saop_element_hash(struct saophash_hash *tb, Datum key)
fcinfo->args[0].value = key;
fcinfo->args[0].isnull = false;
- hash = elements_tab->op->d.hashedscalararrayop.hash_fn_addr(fcinfo);
+ hash = FuncExprCallInvoke(elements_tab->op->d.hashedscalararrayop.hash_fn_addr, fcinfo);
return DatumGetUInt32(hash);
}
@@ -3458,7 +3458,7 @@ saop_hash_element_match(struct saophash_hash *tb, Datum key1, Datum key2)
fcinfo->args[1].value = key2;
fcinfo->args[1].isnull = false;
- result = elements_tab->op->d.hashedscalararrayop.fn_addr(fcinfo);
+ result = FuncExprCallInvoke(elements_tab->op->d.hashedscalararrayop.fn_addr, fcinfo);
return DatumGetBool(result);
}
@@ -3619,7 +3619,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
fcinfo->args[1].value = (Datum) 0;
fcinfo->args[1].isnull = true;
- result = op->d.hashedscalararrayop.fn_addr(fcinfo);
+ result = FuncExprCallInvoke(op->d.hashedscalararrayop.fn_addr, fcinfo);
resultnull = fcinfo->isnull;
}
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2cf6dad768..3b339efbe0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index dbc2827d20..7736813230 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index b6835c2c4c..1f95775011 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -16,6 +16,8 @@
#include "postgres.h"
#include "access/detoast.h"
+#include "access/parallel.h"
+#include "access/xact.h"
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
@@ -109,6 +111,30 @@ fmgr_lookupByName(const char *name)
return NULL;
}
+/*
+ * Invoke a function given pointer to function or handler to be called
+ * and a filled-in FunctionCallInfoBaseData.
+ *
+ * Check function's parallel safety before invoking the funciton.
+ * If function are not allowed to be executed in parallel mode an error is raised.
+ */
+Datum
+FuncExprCallInvoke(PGFunction fn_addr, FunctionCallInfo fcinfo)
+{
+ char parallel_safety = fcinfo->flinfo->fn_parallel;
+
+ if (IsInParallelMode() &&
+ ((IsParallelWorker() &&
+ parallel_safety == PROPARALLEL_RESTRICTED) ||
+ parallel_safety == PROPARALLEL_UNSAFE))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("parallel-safety execution violation of function \"%s\" (%c)",
+ get_func_name(fcinfo->flinfo->fn_oid), parallel_safety)));
+
+ return fn_addr(fcinfo);
+}
+
/*
* This routine fills a FmgrInfo struct, given the OID
* of the function to be called.
@@ -174,6 +200,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */
finfo->fn_addr = fbp->func;
finfo->fn_oid = functionId;
+ finfo->fn_parallel = PROPARALLEL_SAFE;
return;
}
@@ -186,6 +213,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
finfo->fn_nargs = procedureStruct->pronargs;
finfo->fn_strict = procedureStruct->proisstrict;
finfo->fn_retset = procedureStruct->proretset;
+ finfo->fn_parallel = procedureStruct->proparallel;
/*
* If it has prosecdef set, non-null proconfig, or if a plugin wants to
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index c04e6a98d7..34cfaf542c 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,6 +466,7 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
/*
* IsModifySupportedInParallelMode
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index ab7b85c86e..a747700818 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -64,6 +64,7 @@ typedef struct FmgrInfo
void *fn_extra; /* extra space for use by handler */
MemoryContext fn_mcxt; /* memory context to store fn_extra in */
fmNodePtr fn_expr; /* expression parse tree for call, or NULL */
+ char fn_parallel; /* parallel-safety: s/r/u */
} FmgrInfo;
/*
@@ -169,8 +170,8 @@ extern void fmgr_symbol(Oid functionId, char **mod, char **fn);
* the fcinfo->isnull flag before each call, since callees are permitted to
* assume that starts out false.
*/
-#define FunctionCallInvoke(fcinfo) ((* (fcinfo)->flinfo->fn_addr) (fcinfo))
-
+#define FunctionCallInvoke(fcinfo) FuncExprCallInvoke((fcinfo)->flinfo->fn_addr, fcinfo)
+Datum FuncExprCallInvoke(PGFunction fn_addr, FunctionCallInfo fcinfo);
/*-------------------------------------------------------------------------
* Support macros to ease writing fmgr-compatible functions
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..9b9b397bfe
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,530 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel safe;
+alter table names4 parallel safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel safe;
+--
+-- END: setup some tables and data needed by the tests.
+--
+-- 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
+);
+alter table para_insert_p1 parallel safe;
+alter table para_insert_f1 parallel safe;
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+alter table names2 parallel safe;
+insert into names2 select * from names returning *;
+ERROR: parallel-safety execution violation of function "fullname_parallel_unsafe" (u)
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+--
+-- Test INSERT with underlying 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 returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+insert into table_check_b select * from names;
+ERROR: parallel-safety execution violation of function "check_b_unsafe" (u)
+--
+-- Test table with parallel-safe after stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel 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_after_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_after_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_after_trigger_safe
+--
+-- Test table with parallel-unsafe after stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel safe;
+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_after_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_after_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+insert into names_with_unsafe_trigger select * from names;
+ERROR: parallel-safety execution violation of function "insert_after_trigger_unsafe" (u)
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_after_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_after_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+insert into names_with_unsafe_trigger select * from names;
+ERROR: parallel-safety execution violation of function "insert_after_trigger_unsafe" (u)
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a091300857..c6741a98aa 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -95,6 +95,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 5644847601..638b7a23d0 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -151,6 +151,7 @@ test: stats_ext
test: collate.linux.utf8
test: select_parallel
test: write_parallel
+test: insert_parallel
test: publication
test: subscription
test: select_views
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..b505a55caa
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,337 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel safe;
+alter table names4 parallel safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel safe;
+
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+-- 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
+);
+
+alter table para_insert_p1 parallel safe;
+alter table para_insert_f1 parallel safe;
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+alter table names2 parallel safe;
+insert into names2 select * from names returning *;
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+insert into table_check_b select * from names;
+
+--
+-- Test table with parallel-safe after stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel 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_after_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_after_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe after stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel safe;
+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_after_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_after_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_after_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_after_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
v1-POC-0004-fix-testcase-with-wrong-parallel-safety-flag.patchapplication/octet-stream; name=v1-POC-0004-fix-testcase-with-wrong-parallel-safety-flag.patchDownload
From 37e56e57ad0593ab30a0e64c44ca7b0bbb64d9c7 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 21 Apr 2021 15:26:39 +0800
Subject: [PATCH] fix-testcase-with-wrong-parallel-safety-flag
---
src/backend/snowball/snowball_func.sql.in | 4 ++--
src/test/regress/expected/aggregates.out | 1 +
src/test/regress/expected/create_type.out | 4 ++--
src/test/regress/expected/domain.out | 2 +-
src/test/regress/input/create_function_1.source | 4 ++--
src/test/regress/output/create_function_1.source | 4 ++--
src/test/regress/sql/aggregates.sql | 1 +
src/test/regress/sql/create_type.sql | 4 ++--
src/test/regress/sql/domain.sql | 2 +-
9 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/src/backend/snowball/snowball_func.sql.in b/src/backend/snowball/snowball_func.sql.in
index cb1eaca4fb..08bf3397e4 100644
--- a/src/backend/snowball/snowball_func.sql.in
+++ b/src/backend/snowball/snowball_func.sql.in
@@ -21,11 +21,11 @@ SET search_path = pg_catalog;
CREATE FUNCTION dsnowball_init(INTERNAL)
RETURNS INTERNAL AS '$libdir/dict_snowball', 'dsnowball_init'
-LANGUAGE C STRICT;
+LANGUAGE C STRICT PARALLEL SAFE;
CREATE FUNCTION dsnowball_lexize(INTERNAL, INTERNAL, INTERNAL, INTERNAL)
RETURNS INTERNAL AS '$libdir/dict_snowball', 'dsnowball_lexize'
-LANGUAGE C STRICT;
+LANGUAGE C STRICT PARALLEL SAFE;
CREATE TEXT SEARCH TEMPLATE snowball
(INIT = dsnowball_init,
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index ca06d41dd0..2a4a83fab7 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -2386,6 +2386,7 @@ rollback;
BEGIN;
CREATE FUNCTION balkifnull(int8, int4)
RETURNS int8
+PARALLEL SAFE
STRICT
LANGUAGE plpgsql AS $$
BEGIN
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 14394cc95c..eb1c6bdcd2 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -48,7 +48,7 @@ NOTICE: return type int42 is only a shell
CREATE FUNCTION int42_out(int42)
RETURNS cstring
AS 'int4out'
- LANGUAGE internal STRICT IMMUTABLE;
+ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE;
NOTICE: argument type int42 is only a shell
CREATE FUNCTION text_w_default_in(cstring)
RETURNS text_w_default
@@ -58,7 +58,7 @@ NOTICE: return type text_w_default is only a shell
CREATE FUNCTION text_w_default_out(text_w_default)
RETURNS cstring
AS 'textout'
- LANGUAGE internal STRICT IMMUTABLE;
+ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE;
NOTICE: argument type text_w_default is only a shell
CREATE TYPE int42 (
internallength = 4,
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..c82d189823 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -1067,7 +1067,7 @@ drop domain di;
-- this has caused issues in the past
--
create function sql_is_distinct_from(anyelement, anyelement)
-returns boolean language sql
+returns boolean language sql parallel safe
as 'select $1 is distinct from $2 limit 1';
create domain inotnull int
check (sql_is_distinct_from(value, null));
diff --git a/src/test/regress/input/create_function_1.source b/src/test/regress/input/create_function_1.source
index 6c69b7fe6c..b9a4e7af38 100644
--- a/src/test/regress/input/create_function_1.source
+++ b/src/test/regress/input/create_function_1.source
@@ -10,7 +10,7 @@ CREATE FUNCTION widget_in(cstring)
CREATE FUNCTION widget_out(widget)
RETURNS cstring
AS '@libdir@/regress@DLSUFFIX@'
- LANGUAGE C STRICT IMMUTABLE;
+ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
CREATE FUNCTION int44in(cstring)
RETURNS city_budget
@@ -20,7 +20,7 @@ CREATE FUNCTION int44in(cstring)
CREATE FUNCTION int44out(city_budget)
RETURNS cstring
AS '@libdir@/regress@DLSUFFIX@'
- LANGUAGE C STRICT IMMUTABLE;
+ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
CREATE FUNCTION check_primary_key ()
RETURNS trigger
diff --git a/src/test/regress/output/create_function_1.source b/src/test/regress/output/create_function_1.source
index c66146db9d..0c1390e8c5 100644
--- a/src/test/regress/output/create_function_1.source
+++ b/src/test/regress/output/create_function_1.source
@@ -10,7 +10,7 @@ DETAIL: Creating a shell type definition.
CREATE FUNCTION widget_out(widget)
RETURNS cstring
AS '@libdir@/regress@DLSUFFIX@'
- LANGUAGE C STRICT IMMUTABLE;
+ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
NOTICE: argument type widget is only a shell
CREATE FUNCTION int44in(cstring)
RETURNS city_budget
@@ -21,7 +21,7 @@ DETAIL: Creating a shell type definition.
CREATE FUNCTION int44out(city_budget)
RETURNS cstring
AS '@libdir@/regress@DLSUFFIX@'
- LANGUAGE C STRICT IMMUTABLE;
+ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
NOTICE: argument type city_budget is only a shell
CREATE FUNCTION check_primary_key ()
RETURNS trigger
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index eb80a2fe06..68990b5b5f 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -978,6 +978,7 @@ rollback;
BEGIN;
CREATE FUNCTION balkifnull(int8, int4)
RETURNS int8
+PARALLEL SAFE
STRICT
LANGUAGE plpgsql AS $$
BEGIN
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index a32a9e6795..285707e532 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -51,7 +51,7 @@ CREATE FUNCTION int42_in(cstring)
CREATE FUNCTION int42_out(int42)
RETURNS cstring
AS 'int4out'
- LANGUAGE internal STRICT IMMUTABLE;
+ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE;
CREATE FUNCTION text_w_default_in(cstring)
RETURNS text_w_default
AS 'textin'
@@ -59,7 +59,7 @@ CREATE FUNCTION text_w_default_in(cstring)
CREATE FUNCTION text_w_default_out(text_w_default)
RETURNS cstring
AS 'textout'
- LANGUAGE internal STRICT IMMUTABLE;
+ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE;
CREATE TYPE int42 (
internallength = 4,
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 549c0b5adf..a022ae4223 100644
--- a/src/test/regress/sql/domain.sql
+++ b/src/test/regress/sql/domain.sql
@@ -724,7 +724,7 @@ drop domain di;
--
create function sql_is_distinct_from(anyelement, anyelement)
-returns boolean language sql
+returns boolean language sql parallel safe
as 'select $1 is distinct from $2 limit 1';
create domain inotnull int
--
2.18.4
v1-POC-0001-get-parallel-safety-function.patchapplication/octet-stream; name=v1-POC-0001-get-parallel-safety-function.patchDownload
From 202024c8d24ac9fa393b8e3b36bfdcc14d7bae0e Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 21 Apr 2021 19:21:11 +0800
Subject: [PATCH] get-parallel-safety-function
provide a utility function pg_get_parallel_safety('table_name') that returns records of
(objid, classid, parallel_safety) that represent the parallel safety of objects that
determine the parallel safety of the specified table. The user can use this function
to identify problematic objects when a parallel DML fails or is not parallelized in an expected manner.
When detecting an parallel unsafe/restricted function in index(or others can have an expression),
return both the function oid and the index oid.
---
src/backend/optimizer/plan/planner.c | 3 +-
src/backend/optimizer/util/clauses.c | 600 ++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 75 ++++
src/backend/utils/cache/typcache.c | 14 +
src/include/access/xact.h | 14 +
src/include/catalog/pg_proc.dat | 18 +-
src/include/optimizer/clauses.h | 9 +
src/include/utils/typcache.h | 2 +
8 files changed, 729 insertions(+), 6 deletions(-)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..dbc2827d20 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d9ad4efc5e..3ec0bf26cb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,13 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -43,6 +50,9 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +61,8 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all;
+ List *func_oids;
+ PartitionDirectory partition_directory;
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -98,6 +113,20 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static List *target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context);
+static List *target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *index_expr_max_parallel_hazard(Relation index_rel, List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting, max_parallel_hazard_context *context);
+static List *target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static List *target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -149,6 +178,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
* Aggregate-function clause manipulation
@@ -620,6 +650,10 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
+
(void) max_parallel_hazard_walker((Node *) parse, &context);
return context.max_hazard;
}
@@ -651,6 +685,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -682,7 +719,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -699,6 +736,63 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+static bool
+parallel_safety_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ bool max_hazard_found;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+ max_hazard_found = max_parallel_hazard_test(proparallel, cont);
+
+ if ((proparallel != PROPARALLEL_SAFE && cont->check_all) ||
+ max_hazard_found)
+ {
+ cont->func_oids = lappend(cont->func_oids,
+ make_safety_object(func_id, ProcedureRelationId, proparallel));
+ }
+
+ return max_hazard_found && !cont->check_all;
+}
+
+/* Check parallel unsafe/restricted function in expression */
+static bool
+parallel_safety_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_safety_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ if (target_rel_domain_max_parallel_hazard(((CoerceToDomain *)node)->resulttype, context) != NIL &&
+ !context->check_all)
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_safety_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -854,6 +948,510 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+List*
+target_rel_max_parallel_hazard(RangeVar *relrv, bool findall, char max_interesting)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+ List *objects;
+
+ context.check_all = findall;
+ context.func_oids = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_openrv(relrv, AccessShareLock);
+
+ objects = target_rel_all_parallel_hazard_recurse(targetRel, &context);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ return objects;
+}
+
+
+List *
+target_rel_all_parallel_hazard_recurse(Relation rel, max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ Assert(context != NULL && context->check_all);
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context);
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(rel->rd_rel->oid, RelationRelationId, PROPARALLEL_RESTRICTED));
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ (void) target_rel_partitions_max_parallel_hazard(rel, context);
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ (void) target_rel_index_max_parallel_hazard(rel, context);
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ (void) target_rel_trigger_max_parallel_hazard(rel, context);
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef)
+ {
+ Node *defaultexpr;
+ defaultexpr = build_column_default(rel, attnum);
+ parallel_safety_walker((Node *) defaultexpr, context);
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ (void) target_rel_domain_max_parallel_hazard(att->atttypid, context);
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ (void) target_rel_chk_constr_max_parallel_hazard(rel, context);
+
+ return context->func_oids;
+}
+
+/*
+ * target_rel_trigger_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified relation's
+ * trigger data.
+ */
+static List*
+target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ Assert(context != NULL && context->check_all);
+
+ if (rel->trigdesc == NULL)
+ return context->func_oids;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgfoid, ProcedureRelationId, proparallel));
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgoid, TriggerRelationId, proparallel));
+ }
+ }
+
+ return context->func_oids;
+}
+
+static List*
+index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ Form_pg_index indexStruct;
+ int i;
+ ListCell *index_expr_item;
+
+ Assert(context != NULL && context->check_all);
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ if (ii_Expressions != NIL)
+ {
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ /* find some not safe objects */
+ parallel_safety_walker(index_expr, context);
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+ }
+
+ if (ii_Predicate != NIL)
+ parallel_safety_walker((Node *) ii_Predicate, context);
+
+ /*
+ * Check parallel-safety of any index AM support functions.
+ */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+ if (nsupport > 0)
+ {
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+
+ Oid funcOid = index_rel->rd_support[i];
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+ if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ }
+ }
+
+ return context->func_oids;
+}
+
+/*
+ * target_rel_index_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any existing index
+ * expressions or index predicate of a specified relation.
+ */
+static List*
+target_rel_index_max_parallel_hazard(Relation rel, max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+
+ Assert(context != NULL && context->check_all);
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ index_expr_max_parallel_hazard(index_rel, ii_Expressions,
+ ii_Predicate, context->check_all,
+ context->max_interesting,
+ context);
+
+ /* Add the index itself to the objects list */
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(index_oid, IndexRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ index_close(index_rel, lockmode);
+ }
+
+ list_free(index_oid_list);
+
+ return context->func_oids;
+}
+
+/*
+ * target_rel_domain_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified DOMAIN type.
+ * Only any CHECK expressions are examined for parallel-safety.
+ */
+static List*
+target_rel_domain_max_parallel_hazard(Oid typid, max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+
+ Assert(context != NULL && context->check_all);
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) r->check_expr, context);
+
+ /* Add the Constraint itself to the objects list */
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_domain_constraint_oid(typid, r->name, false),
+ ConstraintRelationId,
+ context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return context->func_oids;
+
+}
+
+/*
+ * target_rel_partitions_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any partitions of a
+ * of a specified relation.
+ */
+static List*
+target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs, *qual;
+
+ Assert(context != NULL && context->check_all);
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ parallel_safety_walker((Node *) qual, context);
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return context->func_oids;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+ max_parallel_hazard_test(proparallel, context);
+
+ if (proparallel != PROPARALLEL_SAFE)
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ parallel_safety_walker(check_expr, context);
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ (void) target_rel_all_parallel_hazard_recurse(part_rel, context);
+ table_close(part_rel, AccessShareLock);
+ }
+
+ return context->func_oids;
+}
+
+/*
+ * target_rel_chk_constr_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any CHECK expressions or
+ * CHECK constraints related to the specified relation.
+ */
+static List*
+target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ List *temp_objects;
+
+ Assert(context != NULL && context->check_all);
+
+ tupdesc = RelationGetDescr(rel);
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ if (tupdesc->constr != NULL && tupdesc->constr->num_check > 0)
+ {
+ int i;
+
+ ConstrCheck *check = tupdesc->constr->check;
+
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) check_expr, context);
+
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_relation_constraint_oid(rel->rd_rel->oid, check->ccname, true), ConstraintRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+ }
+
+ return context->func_oids;
+}
+
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4dfd7..33bc6b602d 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,77 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ RangeVar *relvar;
+ text *relname_text;
+ ReturnSetInfo *rsinfo;
+
+ relname_text = PG_GETARG_TEXT_PP(0);
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+
+ MemoryContextSwitchTo(oldcontext);
+
+ relvar = makeRangeVarFromNameList(textToQualifiedNameList(relname_text));
+ objects = target_rel_max_parallel_hazard(relvar, true, PROPARALLEL_UNSAFE);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 4915ef5934..260f5d45c8 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,20 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+
+List *GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index f49a57b35e..c04e6a98d7 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -467,4 +467,18 @@ extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
+
#endif /* XACT_H */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b62abcd22c..a511a03021 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3765,6 +3765,16 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'information about the parallel unsafe or restricted objects in the target table',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3772,11 +3782,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887a85..857d89e0d4 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -52,5 +59,7 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_max_parallel_hazard(RangeVar *relrv, bool findall, char max_interesting);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a4b7..28ca7d8a6e 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.18.4
BACKGROUND
========================================We want to realize parallel INSERT SELECT in the following steps:
1) INSERT + parallel SELECT
2) Parallel INSERT + parallel SELECTBelow are example use cases. We don't expect high concurrency or an
empty data source.
* Data loading (ETL or ELT) into an analytics database, typically a
data ware house.
* Batch processing in an OLTP database.
2) Enabling users to declare that the table allows parallel data
modification Add a table property that represents parallel safety of
the table for DML statement execution. Users specify it as follows:CREATE TABLE table_name (...) PARALLEL { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL { UNSAFE | RESTRICTED | SAFE };This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel. The default is UNSAFE.The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have the specified parallel safety or
safer one. The user is responsible for its correctness. If the
parallel processes find an object that is less safer than the assumed
parallel safety during statement execution, it throws an ERROR and abort thestatement execution.
When the parallel safety of some of these objects is changed, it's
costly to reflect it on the parallel safety of tables that depend on
them. So, we don't do it. Instead, we provide a utility function
pg_get_parallel_safety('table_name')
that returns records of (objid, classid, parallel_safety) that
represent the parallel safety of objects that determine the parallel
safety of the specified table. The function only outputs objects that
are not parallel safe. Otherwise, it will consume excessive memory
while accumulating the output. The user can use this function to
identify problematic objects when a parallel DML fails or is not parallelized inan expected manner.
How does the executor detect parallel unsafe objects? There are two ways:
1) At loading time
...
2) At function execution time
All related objects come down to some function execution. So, add a
parallel safety check there when in a parallel worker. If the current
process is a parallel worker and the function is parallel unsafe,
error out with ereport(ERROR). This approach eliminates the oversight
of parallel safety check with the additional bonus of tiny code change!The place would be FunctionCallInvoke(). It's a macro in fmgr.h now.
Perhaps we should make it a function in fmgr.c, so that fmgr.h does
not have to include header files for parallelism-related definitions.We have to evaluate the performance effect of converting
FunctionCallInvoke() into a function and adding an if statement there,
because it's a relatively low-level function.Based on above, we plan to move forward with the apporache 2) (declarative
idea).Attatching the POC patchset which including the following:
0001: provide a utility function pg_get_parallel_safety('table_name').
The function returns records of (objid, classid, parallel_safety) that represent
the parallel safety of objects that determine the parallel safety of the
specified table.
Note: The function only outputs objects that are not parallel safe.
(Thanks a lot for greg's previous work, most of the safety check code here is
based on it)0002: allow user use "ALTER TABLE PARALLEL SAFE/UNSAFE/RESTRICTED".
Add proparallel column in pg_class and allow use to change its.
0003: detect parallel unsafe objects in executor.
Currently we choose to check function's parallel safety at function execution
time.
We add safety check at FunctionCallInvoke(), but it may be better to check in
fmgr_info_cxt_security.
we are still discussing it in another thread[1].TODO: we currently skip checking built-in function's parallel safety, because
we lack the information about built-in
function's parallel safety, we cannot access pg_proc.proparallel in a low level
because it could result in infinite recursion.
Adding parallel property in fmgrBuiltin will enlarge the frequently accessed
fmgr_builtins and lock down the value of the
parallel-safety flag. The solution is still under discussion. Suggestions and
comments are welcome.0004: fix some mislabeled function in testcase
Since we check parallel safety of function at a low level, we found some
functions marked as parallel unsafe will be
executed in parallel mode in regression test when setting
force_parallel_mode=regress. After checking, these functions
are parallel safe, So , we plan to fix these function's parallel label.
Note: we plan to take 0004 as a separate patch , see[2], I post 0004 here just
to prevent some testcase failures.The above are the POC patches, it could be imperfect for now and I am still
working on improving it.
Suggestions and comments about the design or code are very welcome and
appreciated.
Sorry, I forgot to attach the discussion link about [1]/messages/by-id/756027.1619012086@sss.pgh.pa.us and [2]/messages/by-id/OS0PR01MB571637085C0D3AFC3AB3600194479@OS0PR01MB5716.jpnprd01.prod.outlook.com.
[1]: /messages/by-id/756027.1619012086@sss.pgh.pa.us
/messages/by-id/756027.1619012086@sss.pgh.pa.us
[2]: /messages/by-id/OS0PR01MB571637085C0D3AFC3AB3600194479@OS0PR01MB5716.jpnprd01.prod.outlook.com
/messages/by-id/OS0PR01MB571637085C0D3AFC3AB3600194479@OS0PR01MB5716.jpnprd01.prod.outlook.com
Best regards,
houzj
On Thu, Apr 22, 2021 at 4:51 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
BACKGROUND
========================================We want to realize parallel INSERT SELECT in the following steps:
1) INSERT + parallel SELECT
2) Parallel INSERT + parallel SELECTBelow are example use cases. We don't expect high concurrency or an empty
data source.
* Data loading (ETL or ELT) into an analytics database, typically a data ware
house.
* Batch processing in an OLTP database.
2) Enabling users to declare that the table allows parallel data modification Add
a table property that represents parallel safety of the table for DML statement
execution. Users specify it as follows:CREATE TABLE table_name (...) PARALLEL { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL { UNSAFE | RESTRICTED | SAFE };This property is recorded in pg_class's relparallel column as 'u', 'r', or 's', just
like pg_proc's proparallel. The default is UNSAFE.The planner assumes that all of the table, its descendant partitions, and their
ancillary objects have the specified parallel safety or safer one. The user is
responsible for its correctness. If the parallel processes find an object that is
less safer than the assumed parallel safety during statement execution, it
throws an ERROR and abort the statement execution.When the parallel safety of some of these objects is changed, it's costly to
reflect it on the parallel safety of tables that depend on them. So, we don't do
it. Instead, we provide a utility function pg_get_parallel_safety('table_name')
that returns records of (objid, classid, parallel_safety) that represent the
parallel safety of objects that determine the parallel safety of the specified
table. The function only outputs objects that are not parallel safe. Otherwise,
it will consume excessive memory while accumulating the output. The user
can use this function to identify problematic objects when a parallel DML fails
or is not parallelized in an expected manner.How does the executor detect parallel unsafe objects? There are two ways:
1) At loading time
...
2) At function execution time
All related objects come down to some function execution. So, add a parallel
safety check there when in a parallel worker. If the current process is a parallel
worker and the function is parallel unsafe, error out with ereport(ERROR). This
approach eliminates the oversight of parallel safety check with the additional
bonus of tiny code change!The place would be FunctionCallInvoke(). It's a macro in fmgr.h now. Perhaps
we should make it a function in fmgr.c, so that fmgr.h does not have to include
header files for parallelism-related definitions.We have to evaluate the performance effect of converting FunctionCallInvoke()
into a function and adding an if statement there, because it's a relatively
low-level function.Based on above, we plan to move forward with the apporache 2) (declarative idea).
IIUC, the declarative behaviour idea attributes parallel
safe/unsafe/restricted tags to each table with default being the
unsafe. Does it mean for a parallel unsafe table, no parallel selects,
inserts (may be updates) will be picked up? Or is it only the parallel
inserts? If both parallel inserts, selects will be picked, then the
existing tables need to be adjusted to set the parallel safety tags
while migrating?
Another point, what does it mean a table being parallel restricted?
What should happen if it is present in a query of other parallel safe
tables?
I may be wrong here: IIUC, the main problem we are trying to solve
with the declarative approach is to let the user decide parallel
safety for partition tables as it may be costlier for postgres to
determine it. And for the normal tables we can perform parallel safety
checks without incurring much cost. So, I think we should restrict the
declarative approach to only partitioned tables?
While reading the design, I came across this "erroring out during
execution of a query when a parallel unsafe function is detected". If
this is correct, isn't it warranting users to run
pg_get_parallel_safety to know the parallel unsafe objects, set
parallel safety to all of them if possible, otherwise disable
parallelism to run the query? Isn't this burdensome? Instead, how
about postgres retries the query upon detecting the error that came
from a parallel unsafe function during execution, disable parallelism
and run the query? I think this kind of retry query feature can be
built outside of the core postgres, but IMO it will be good to have
inside (of course configurable). IIRC, the Teradata database has a
Query Retry feature.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Based on above, we plan to move forward with the apporache 2) (declarative
idea).
IIUC, the declarative behaviour idea attributes parallel safe/unsafe/restricted
tags to each table with default being the unsafe. Does it mean for a parallel
unsafe table, no parallel selects, inserts (may be updates) will be picked up? Or
is it only the parallel inserts? If both parallel inserts, selects will be picked, then
the existing tables need to be adjusted to set the parallel safety tags while
migrating?
Thanks for looking into this.
The parallel attributes in table means the parallel safety when user does some data-modification operations on it.
So, It only limit the use of parallel plan when using INSERT/UPDATE/DELETE.
Another point, what does it mean a table being parallel restricted?
What should happen if it is present in a query of other parallel safe tables?
If a table is parallel restricted, it means the table contains some parallel restricted objects(such as: parallel restricted functions in index expressions).
And in planner, it means parallel insert plan will not be chosen, but it can use parallel select(with serial insert).
I may be wrong here: IIUC, the main problem we are trying to solve with the
declarative approach is to let the user decide parallel safety for partition tables
as it may be costlier for postgres to determine it. And for the normal tables we
can perform parallel safety checks without incurring much cost. So, I think we
should restrict the declarative approach to only partitioned tables?
Yes, we are tring to avoid overhead when checking parallel safety.
The cost to check all the partition's parallel safety is the biggest one.
Another is the safety check of index's expression.
Currently, for INSERT, the planner does not open the target table's indexinfo and does not
parse the expression of the index. We need to parse the expression in planner if we want
to do parallel safety check for it which can bring some overhead(it will open the index the do the parse in executor again).
So, we plan to skip all of the extra check and let user take responsibility for the safety.
Of course, maybe we can try to pass the indexinfo to the executor but it need some further refactor and I will take a look into it.
While reading the design, I came across this "erroring out during execution of a
query when a parallel unsafe function is detected". If this is correct, isn't it
warranting users to run pg_get_parallel_safety to know the parallel unsafe
objects, set parallel safety to all of them if possible, otherwise disable
parallelism to run the query? Isn't this burdensome?
How about:
If detecting parallel unsafe objects in executor, then, alter the table to parallel unsafe internally.
So, user do not need to alter it manually.
Instead, how about
postgres retries the query upon detecting the error that came from a parallel
unsafe function during execution, disable parallelism and run the query? I think
this kind of retry query feature can be built outside of the core postgres, but
IMO it will be good to have inside (of course configurable). IIRC, the Teradata
database has a Query Retry feature.
Thanks for the suggestion.
The retry query feature sounds like a good idea to me.
OTOH, it sounds more like an independent feature which parallel select can also benefit from it.
I think maybe we can try to achieve it after we commit the parallel insert ?
Best regards,
houzj
On Mon, Apr 26, 2021 at 7:00 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Instead, how about
postgres retries the query upon detecting the error that came from a parallel
unsafe function during execution, disable parallelism and run the query? I think
this kind of retry query feature can be built outside of the core postgres, but
IMO it will be good to have inside (of course configurable). IIRC, the Teradata
database has a Query Retry feature.Thanks for the suggestion.
The retry query feature sounds like a good idea to me.
OTOH, it sounds more like an independent feature which parallel select can also benefit from it.
+1. I also think retrying a query on an error is not related to this
feature and should be built separately if required.
--
With Regards,
Amit Kapila.
On Mon, Apr 26, 2021 at 7:00 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Based on above, we plan to move forward with the apporache 2) (declarative
idea).
IIUC, the declarative behaviour idea attributes parallel safe/unsafe/restricted
tags to each table with default being the unsafe. Does it mean for a parallel
unsafe table, no parallel selects, inserts (may be updates) will be picked up? Or
is it only the parallel inserts? If both parallel inserts, selects will be picked, then
the existing tables need to be adjusted to set the parallel safety tags while
migrating?Thanks for looking into this.
Thanks for the responses.
The parallel attributes in table means the parallel safety when user does some data-modification operations on it.
So, It only limit the use of parallel plan when using INSERT/UPDATE/DELETE.
In that case, isn't it better to use the terminology "PARALLEL DML
SAFE/UNSAFE/RESTRICTED" in the code and docs? This way, it will be
clear that these tags don't affect parallel selects.
Another point, what does it mean a table being parallel restricted?
What should happen if it is present in a query of other parallel safe tables?If a table is parallel restricted, it means the table contains some parallel restricted objects(such as: parallel restricted functions in index expressions).
And in planner, it means parallel insert plan will not be chosen, but it can use parallel select(with serial insert).
Makes sense. I assume that when there is a parallel restricted
function associated with a table, the current design doesn't enforce
the planner to choose parallel select and it is left up to it.
I may be wrong here: IIUC, the main problem we are trying to solve with the
declarative approach is to let the user decide parallel safety for partition tables
as it may be costlier for postgres to determine it. And for the normal tables we
can perform parallel safety checks without incurring much cost. So, I think we
should restrict the declarative approach to only partitioned tables?Yes, we are tring to avoid overhead when checking parallel safety.
The cost to check all the partition's parallel safety is the biggest one.
Another is the safety check of index's expression.
Currently, for INSERT, the planner does not open the target table's indexinfo and does not
parse the expression of the index. We need to parse the expression in planner if we want
to do parallel safety check for it which can bring some overhead(it will open the index the do the parse in executor again).
So, we plan to skip all of the extra check and let user take responsibility for the safety.
Of course, maybe we can try to pass the indexinfo to the executor but it need some further refactor and I will take a look into it.
Will the planner parse and check parallel safety of index((where
clause) expressions in case of SELECTs? I'm not sure of this. But if
it does, maybe we could do the same thing for parallel DML as well for
normal tables? What is the overhead of parsing index expressions? If
the cost is heavy for checking index expressions parallel safety in
case of normal tables, then the current design i.e. attributing
parallel safety tag to all the tables makes sense.
I was actually thinking that we will have the declarative approach
only for partitioned tables as it is the main problem we are trying to
solve with this design. Something like: users will run
pg_get_parallel_safety to see the parallel unsafe objects associated
with a partitioned table by looking at all of its partitions and be
able to set a parallel dml safety tag to only partitioned tables.
While reading the design, I came across this "erroring out during execution of a
query when a parallel unsafe function is detected". If this is correct, isn't it
warranting users to run pg_get_parallel_safety to know the parallel unsafe
objects, set parallel safety to all of them if possible, otherwise disable
parallelism to run the query? Isn't this burdensome?How about:
If detecting parallel unsafe objects in executor, then, alter the table to parallel unsafe internally.
So, user do not need to alter it manually.
I don't think this is a good idea, because, if there are multiple
tables involved in the query, do you alter all the tables? Usually, we
error out on finding the first such unsafe object.
Instead, how about
postgres retries the query upon detecting the error that came from a parallel
unsafe function during execution, disable parallelism and run the query? I think
this kind of retry query feature can be built outside of the core postgres, but
IMO it will be good to have inside (of course configurable). IIRC, the Teradata
database has a Query Retry feature.Thanks for the suggestion.
The retry query feature sounds like a good idea to me.
OTOH, it sounds more like an independent feature which parallel select can also benefit from it.
I think maybe we can try to achieve it after we commit the parallel insert ?
Yeah, it will be a separate thing altogether.
0001: provide a utility function pg_get_parallel_safety('table_name').
The function returns records of (objid, classid, parallel_safety) that represent
the parallel safety of objects that determine the parallel safety of the specified table.
Note: The function only outputs objects that are not parallel safe.
If it returns only parallel "unsafe" objects and not "safe" or
"restricted" objects, how about naming it to
pg_get_table_parallel_unsafe_objects("table_name")? This way we could
get rid of parallel_safety in the output record? If at all users want
to see parallel restricted or safe objects, we can also have the
counterparts pg_get_table_parallel_safe_objects and
pg_get_table_parallel_restricted_objects. Of course, we can caution
the user that execution of these functions might take longer and
"might consume excessive memory while accumulating the output".
Otherwise, we can have a single function
pg_get_parallel_safety("table_name" IN, "parallel_safety" IN, "objid"
OUT, "classid" OUT)? If required, we could name it
pg_get_parallel_safety_of_table_objects.
Thoughts?
Although, I have not looked at the patches, few questions on
pg_get_parallel_safety function:
1) Will it parse all the expressions for the objects that are listed
under "The objects that relate to the parallel safety of a DML target
table are as follows:" in the upthread?
2) How will it behave if a partitioned table is passed to it? Will it
recurse for all the partitions?
3) How will it behave if a foreign table is passed to it? Will it error out?
In general:
1) Is ALTER SET PARALLEL SAFETY on a partitioned table allowed? If
yes, will it be set based on all the partitions parallel safety?
2) How will users have to decide on parallel safety of a foreign table
or a partitioned table with foreign partitions? Or is it that we set
these tables parallel unsafe and don't do parallel inserts?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
The parallel attributes in table means the parallel safety when user does some
data-modification operations on it.
So, It only limit the use of parallel plan when using INSERT/UPDATE/DELETE.
In that case, isn't it better to use the terminology "PARALLEL DML
SAFE/UNSAFE/RESTRICTED" in the code and docs? This way, it will be clear that
these tags don't affect parallel selects.
Makes sense, I recalled I have heart similar suggestion before.
If there are no other objections, I plan to change command to
PARALLEL DML in my next version patches.
I may be wrong here: IIUC, the main problem we are trying to solve
with the declarative approach is to let the user decide parallel
safety for partition tables as it may be costlier for postgres to
determine it. And for the normal tables we can perform parallel
safety checks without incurring much cost. So, I think we should restrict thedeclarative approach to only partitioned tables?
Yes, we are tring to avoid overhead when checking parallel safety.
The cost to check all the partition's parallel safety is the biggest one.
Another is the safety check of index's expression.
Currently, for INSERT, the planner does not open the target table's
indexinfo and does not parse the expression of the index. We need to
parse the expression in planner if we want to do parallel safety check for itwhich can bring some overhead(it will open the index the do the parse in
executor again).So, we plan to skip all of the extra check and let user take responsibility for
the safety.
Of course, maybe we can try to pass the indexinfo to the executor but it need
some further refactor and I will take a look into it.
Will the planner parse and check parallel safety of index((where
clause) expressions in case of SELECTs? I'm not sure of this. But if it does, maybe
we could do the same thing for parallel DML as well for normal tables?
The planner does not check the index expression, because the expression will not be used in SELECT.
I think the expression is only used when a tuple inserted into the index.
the overhead of parsing index expressions? If the cost is heavy for checking
index expressions parallel safety in case of normal tables, then the current
design i.e. attributing parallel safety tag to all the tables makes sense.
Currently, index expression and predicate are stored in text format.
We need to use stringToNode(expression/predicate) to parse it.
Some committers think doing this twice does not look good,
unless we found some ways to pass parsed info to the executor to avoid the second parse.
I was actually thinking that we will have the declarative approach only for
partitioned tables as it is the main problem we are trying to solve with this
design. Something like: users will run pg_get_parallel_safety to see the parallel
unsafe objects associated with a partitioned table by looking at all of its
partitions and be able to set a parallel dml safety tag to only partitioned tables.
We originally want to make the declarative approach for both normal and partitioned table.
In this way, it will not bring any overhead to planner and looks consistency.
But, do you think we should put some really cheap safety check to the planner ?
0001: provide a utility function pg_get_parallel_safety('table_name').
The function returns records of (objid, classid, parallel_safety) that
represent the parallel safety of objects that determine the parallel safety ofthe specified table.
Note: The function only outputs objects that are not parallel safe.
If it returns only parallel "unsafe" objects and not "safe" or "restricted" objects,
how about naming it to pg_get_table_parallel_unsafe_objects("table_name")?
Currently, the function returns both unsafe and restricted objects(I thought restricted is also not safe),
because we thought users only care about the objects that affect the use of parallel plan.
This way we could get rid of parallel_safety in the output record? If at all users
want to see parallel restricted or safe objects, we can also have the
counterparts pg_get_table_parallel_safe_objects and
pg_get_table_parallel_restricted_objects. Of course, we can caution the user
that execution of these functions might take longer and "might consume
excessive memory while accumulating the output".Otherwise, we can have a single function pg_get_parallel_safety("table_name"
IN, "parallel_safety" IN, "objid"
OUT, "classid" OUT)? If required, we could name it
pg_get_parallel_safety_of_table_objects.Thoughts?
I am sure will user want to get safe objects, do you have some usecases ?
If users do not care the safe objects, I think they can use
"SELECT * FROM pg_get_parallel_safety() where parallel_safety = 'specified safety' " to get the specified objects.
Although, I have not looked at the patches, few questions on
pg_get_parallel_safety function:
1) Will it parse all the expressions for the objects that are listed under "The
objects that relate to the parallel safety of a DML target table are as follows:" in
the upthread?
Yes.
But some parsed expression(such as domain type's expression) can be found in typecache,
we just check the safety for these already parsed expression.
2) How will it behave if a partitioned table is passed to it? Will it recurse for all
the partitions?
Yes, because both parent table and child table will be inserted and the parallel
related objects on them will be executed. If users want to make sure the parallel insert succeed,
they need to check all the objects.
3) How will it behave if a foreign table is passed to it? Will it error out?
It currently does not error out.
It will also check the objects on it and return not safe objects.
Note: I consider foreign table itself as a parallel restricted object, because it does not support parallel insert fdw api for now.
In general:
1) Is ALTER SET PARALLEL SAFETY on a partitioned table allowed?
Yes.
If yes, will it be
set based on all the partitions parallel safety?
Yes,
But the ALTER PARALLEL command itself does not check all the partition's safety flag.
The function pg_get_parallel_safety can return all the partition's not safe objects,
user should set the parallel safety based the function's result.
2) How will users have to decide on parallel safety of a foreign table or a
partitioned table with foreign partitions? Or is it that we set these tables
parallel unsafe and don't do parallel inserts?
Foreign table itself is considered as parallel restricted,
because we do not support parallel insert fdw api for now.
Best regards,
houzj
On Mon, Apr 26, 2021 at 4:56 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
The parallel attributes in table means the parallel safety when user does some
data-modification operations on it.
So, It only limit the use of parallel plan when using INSERT/UPDATE/DELETE.
In that case, isn't it better to use the terminology "PARALLEL DML
SAFE/UNSAFE/RESTRICTED" in the code and docs? This way, it will be clear that
these tags don't affect parallel selects.Makes sense, I recalled I have heart similar suggestion before.
If there are no other objections, I plan to change command to
PARALLEL DML in my next version patches.
+1 from me. Let's hear from others.
Will the planner parse and check parallel safety of index((where
clause) expressions in case of SELECTs? I'm not sure of this. But if it does, maybe
we could do the same thing for parallel DML as well for normal tables?The planner does not check the index expression, because the expression will not be used in SELECT.
I think the expression is only used when a tuple inserted into the index.
Oh.
the overhead of parsing index expressions? If the cost is heavy for checking
index expressions parallel safety in case of normal tables, then the current
design i.e. attributing parallel safety tag to all the tables makes sense.Currently, index expression and predicate are stored in text format.
We need to use stringToNode(expression/predicate) to parse it.
Some committers think doing this twice does not look good,
unless we found some ways to pass parsed info to the executor to avoid the second parse.
How much is the extra cost that's added if we do stringToNode twiice?
Maybe, we can check for a few sample test cases by just having
stringToNode(expression/predicate) in the planner and see if it adds
much to the total execution time of the query.
I was actually thinking that we will have the declarative approach only for
partitioned tables as it is the main problem we are trying to solve with this
design. Something like: users will run pg_get_parallel_safety to see the parallel
unsafe objects associated with a partitioned table by looking at all of its
partitions and be able to set a parallel dml safety tag to only partitioned tables.We originally want to make the declarative approach for both normal and partitioned table.
In this way, it will not bring any overhead to planner and looks consistency.
But, do you think we should put some really cheap safety check to the planner ?
I still feel that why we shouldn't limit the declarative approach to
only partitioned tables? And for normal tables, possibly with a
minimal cost(??), the server can do the safety checking. I know this
feels a little inconsistent. In the planner we will have different
paths like: if (partitioned_table) { check the parallel safety tag
associated with the table } else { perform the parallel safety of the
associated objects }.
Others may have better thoughts on this.
0001: provide a utility function pg_get_parallel_safety('table_name').
The function returns records of (objid, classid, parallel_safety) that
represent the parallel safety of objects that determine the parallel safety ofthe specified table.
Note: The function only outputs objects that are not parallel safe.
If it returns only parallel "unsafe" objects and not "safe" or "restricted" objects,
how about naming it to pg_get_table_parallel_unsafe_objects("table_name")?Currently, the function returns both unsafe and restricted objects(I thought restricted is also not safe),
because we thought users only care about the objects that affect the use of parallel plan.
Hm.
This way we could get rid of parallel_safety in the output record? If at all users
want to see parallel restricted or safe objects, we can also have the
counterparts pg_get_table_parallel_safe_objects and
pg_get_table_parallel_restricted_objects. Of course, we can caution the user
that execution of these functions might take longer and "might consume
excessive memory while accumulating the output".Otherwise, we can have a single function pg_get_parallel_safety("table_name"
IN, "parallel_safety" IN, "objid"
OUT, "classid" OUT)? If required, we could name it
pg_get_parallel_safety_of_table_objects.Thoughts?
I am sure will user want to get safe objects, do you have some usecases ?
If users do not care the safe objects, I think they can use
"SELECT * FROM pg_get_parallel_safety() where parallel_safety = 'specified safety' " to get the specified objects.
I don't know any practical scenarios, but If I'm a user, at least I
will be tempted to see the parallel safe objects associated with a
particular table along with unsafe and restricted ones. Others may
have better thoughts on this.
Although, I have not looked at the patches, few questions on
pg_get_parallel_safety function:
1) Will it parse all the expressions for the objects that are listed under "The
objects that relate to the parallel safety of a DML target table are as follows:" in
the upthread?Yes.
But some parsed expression(such as domain type's expression) can be found in typecache,
we just check the safety for these already parsed expression.
Oh.
2) How will it behave if a partitioned table is passed to it? Will it recurse for all
the partitions?Yes, because both parent table and child table will be inserted and the parallel
related objects on them will be executed. If users want to make sure the parallel insert succeed,
they need to check all the objects.
Then, running the pg_get_parallel_safety will have some overhead if
there are many partitions associated with a table. And, this is the
overhead planner would have had to incur without the declarative
approach which we are trying to avoid with this design.
3) How will it behave if a foreign table is passed to it? Will it error out?
It currently does not error out.
It will also check the objects on it and return not safe objects.
Note: I consider foreign table itself as a parallel restricted object, because it does not support parallel insert fdw api for now.2) How will users have to decide on parallel safety of a foreign table or a
partitioned table with foreign partitions? Or is it that we set these tables
parallel unsafe and don't do parallel inserts?Foreign table itself is considered as parallel restricted,
because we do not support parallel insert fdw api for now.
Maybe, the ALTER TABLE ... SET PARALLEL on a foreign table should
default to parallel restricted always and emit a warning saying the
reason?
In general:
1) Is ALTER SET PARALLEL SAFETY on a partitioned table allowed?Yes.
If yes, will it be
set based on all the partitions parallel safety?Yes,
But the ALTER PARALLEL command itself does not check all the partition's safety flag.
The function pg_get_parallel_safety can return all the partition's not safe objects,
user should set the parallel safety based the function's result.
I'm thinking that when users say ALTER TABLE partioned_table SET
PARALLEL TO 'safe';, we check all the partitions' and their associated
objects' parallel safety? If all are parallel safe, then only we set
partitioned_table as parallel safe. What should happen if the parallel
safety of any of the associated objects/partitions changes after
setting the partitioned_table safety?
My understanding was that: the command ALTER TABLE ... SET PARALLEL TO
'safe' work will check the parallel safety of all the objects
associated with the table. If the objects are all parallel safe, then
the table will be set to safe. If at least one object is parallel
unsafe or restricted, then the command will fail. I was also thinking
that how will the design cope with situations such as the parallel
safety of any of the associated objects changing after setting the
table to parallel safe. The planner would have relied on the outdated
parallel safety of the table and chosen parallel inserts and the
executor will catch such situations. Looks like my understanding was
wrong.
So, the ALTER TABLE ... SET PARALLEL TO command just sets the target
table safety, doesn't bother what the associated objects' safety is.
It just believes the user. If at all there are any parallel unsafe
objects it will be caught by the executor. Just like, setting parallel
safety of the functions/aggregates, the docs caution users about
accidentally/intentionally tagging parallel unsafe
functions/aggregates as parallel safe.
Note: I meant the table objects are the ones that are listed under
"The objects that relate to the parallel safety of a DML target table
are as follows:" in the upthread.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Currently, index expression and predicate are stored in text format.
We need to use stringToNode(expression/predicate) to parse it.
Some committers think doing this twice does not look good, unless we
found some ways to pass parsed info to the executor to avoid the secondparse.
How much is the extra cost that's added if we do stringToNode twiice?
Maybe, we can check for a few sample test cases by just having
stringToNode(expression/predicate) in the planner and see if it adds much to
the total execution time of the query.
OK, I will do some test on it.
Yes, because both parent table and child table will be inserted and
the parallel related objects on them will be executed. If users want
to make sure the parallel insert succeed, they need to check all the objects.Then, running the pg_get_parallel_safety will have some overhead if there are
many partitions associated with a table. And, this is the overhead planner
would have had to incur without the declarative approach which we are trying
to avoid with this design.
Yes, I think put such overhead in a separate function is better than in a common path(planner).
Foreign table itself is considered as parallel restricted, because we
do not support parallel insert fdw api for now.Maybe, the ALTER TABLE ... SET PARALLEL on a foreign table should default to
parallel restricted always and emit a warning saying the reason?
Thanks for the comment, I agree.
I will change this in the next version patches.
But the ALTER PARALLEL command itself does not check all the partition's
safety flag.
The function pg_get_parallel_safety can return all the partition's not
safe objects, user should set the parallel safety based the function's result.I'm thinking that when users say ALTER TABLE partioned_table SET PARALLEL
TO 'safe';, we check all the partitions' and their associated objects' parallel
safety? If all are parallel safe, then only we set partitioned_table as parallel safe.
What should happen if the parallel safety of any of the associated
objects/partitions changes after setting the partitioned_table safety?
Currently, nothing happened if any of the associated objects/partitions changes after setting the partitioned_table safety.
Because , we do not have a really cheap way to catch the change. The existing relcache does not work because alter function
does not invalid the relcache which the function belongs to. And it will bring some other overhead(locking, systable scan,...)
to find the table the objects belong to.
My understanding was that: the command ALTER TABLE ... SET PARALLEL TO
'safe' work will check the parallel safety of all the objects associated with the
table. If the objects are all parallel safe, then the table will be set to safe. If at
least one object is parallel unsafe or restricted, then the command will fail.
I think this idea makes sense. Some detail of the designed can be improved.
I agree with you that we can try to check check all the partitions' and their associated objects' parallel safety when ALTER PARALLEL.
Because it's a separate new command, add some overhead to it seems not too bad.
If there are no other objections, I plan to add safety check in the ALTER PARALLEL command.
also thinking that how will the design cope with situations such as the parallel
safety of any of the associated objects changing after setting the table to
parallel safe. The planner would have relied on the outdated parallel safety of
the table and chosen parallel inserts and the executor will catch such situations.
Looks like my understanding was wrong.
Currently, we assume user is responsible for its correctness.
Because, from our research, when the parallel safety of some of these objects is changed,
it's costly to reflect it on the parallel safety of tables that depend on them.
(we need to scan the pg_depend,pg_inherit,pg_index.... to find the target table)
So, the ALTER TABLE ... SET PARALLEL TO command just sets the target table
safety, doesn't bother what the associated objects' safety is.
It just believes the user. If at all there are any parallel unsafe objects it will be
caught by the executor. Just like, setting parallel safety of the
functions/aggregates, the docs caution users about accidentally/intentionally
tagging parallel unsafe functions/aggregates as parallel safe.
Yes, thanks for looking into this.
Best regards,
houzj
On Tue, Apr 27, 2021 at 10:51 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
I still feel that why we shouldn't limit the declarative approach to
only partitioned tables? And for normal tables, possibly with a
minimal cost(??), the server can do the safety checking. I know this
feels a little inconsistent. In the planner we will have different
paths like: if (partitioned_table) { check the parallel safety tag
associated with the table } else { perform the parallel safety of the
associated objects }.
Personally I think the simplest and best approach is just do it
consistently, using the declarative approach across all table types.
Then, running the pg_get_parallel_safety will have some overhead if
there are many partitions associated with a table. And, this is the
overhead planner would have had to incur without the declarative
approach which we are trying to avoid with this design.
The big difference is that pg_get_parallel_safety() is intended to be
used during development, not as part of runtime parallel-safety checks
(which are avoided using the declarative approach).
So there is no runtime overhead associated with pg_get_parallel_safety().
I'm thinking that when users say ALTER TABLE partioned_table SET
PARALLEL TO 'safe';, we check all the partitions' and their associated
objects' parallel safety? If all are parallel safe, then only we set
partitioned_table as parallel safe. What should happen if the parallel
safety of any of the associated objects/partitions changes after
setting the partitioned_table safety?
With the declarative approach, there is no parallel-safety checking on
either the CREATE/ALTER when the parallel-safety is declared/set.
It's up to the user to get it right. If it's actually wrong, it will
be detected at runtime.
Regards,
Greg Nancarrow
Fujitsu Australia
On Tue, Apr 27, 2021 at 7:45 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Apr 27, 2021 at 10:51 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I still feel that why we shouldn't limit the declarative approach to
only partitioned tables? And for normal tables, possibly with a
minimal cost(??), the server can do the safety checking. I know this
feels a little inconsistent. In the planner we will have different
paths like: if (partitioned_table) { check the parallel safety tag
associated with the table } else { perform the parallel safety of the
associated objects }.Personally I think the simplest and best approach is just do it
consistently, using the declarative approach across all table types.
Yeah, if we decide to go with a declarative approach then that sounds
like a better approach.
Then, running the pg_get_parallel_safety will have some overhead if
there are many partitions associated with a table. And, this is the
overhead planner would have had to incur without the declarative
approach which we are trying to avoid with this design.The big difference is that pg_get_parallel_safety() is intended to be
used during development, not as part of runtime parallel-safety checks
(which are avoided using the declarative approach).
So there is no runtime overhead associated with pg_get_parallel_safety().I'm thinking that when users say ALTER TABLE partioned_table SET
PARALLEL TO 'safe';, we check all the partitions' and their associated
objects' parallel safety? If all are parallel safe, then only we set
partitioned_table as parallel safe. What should happen if the parallel
safety of any of the associated objects/partitions changes after
setting the partitioned_table safety?With the declarative approach, there is no parallel-safety checking on
either the CREATE/ALTER when the parallel-safety is declared/set.
It's up to the user to get it right. If it's actually wrong, it will
be detected at runtime.
OTOH, even if we want to verify at DDL time, we won't be able to
maintain it at the later point of time say if user changed the
parallel-safety of some function used in check constraint. I think the
function pg_get_parallel_safety() will help the user to decide whether
it can declare table parallel-safe. Now, it is quite possible that the
user can later change the parallel-safe property of some function then
that should be caught at runtime.
--
With Regards,
Amit Kapila.
On Tue, Apr 27, 2021 at 7:39 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
I'm thinking that when users say ALTER TABLE partioned_table SET PARALLEL
TO 'safe';, we check all the partitions' and their associated objects' parallel
safety? If all are parallel safe, then only we set partitioned_table as parallel safe.
What should happen if the parallel safety of any of the associated
objects/partitions changes after setting the partitioned_table safety?Currently, nothing happened if any of the associated objects/partitions changes after setting the partitioned_table safety.
Because , we do not have a really cheap way to catch the change. The existing relcache does not work because alter function
does not invalid the relcache which the function belongs to. And it will bring some other overhead(locking, systable scan,...)
to find the table the objects belong to.
Makes sense. Anyways, the user is responsible for such changes and
otherwise the executor can catch them at run time, if not, the users
will see unintended consequences.
My understanding was that: the command ALTER TABLE ... SET PARALLEL TO
'safe' work will check the parallel safety of all the objects associated with the
table. If the objects are all parallel safe, then the table will be set to safe. If at
least one object is parallel unsafe or restricted, then the command will fail.I think this idea makes sense. Some detail of the designed can be improved.
I agree with you that we can try to check check all the partitions' and their associated objects' parallel safety when ALTER PARALLEL.
Because it's a separate new command, add some overhead to it seems not too bad.
If there are no other objections, I plan to add safety check in the ALTER PARALLEL command.
Maybe we can make the parallel safety check of the associated
objects/partitions optional for CREATE/ALTER DDLs, with the default
being no checks performed. Both Greg and Amit agree that we don't have
to perform any parallel safety checks during CREATE/ALTER DDLs.
also thinking that how will the design cope with situations such as the parallel
safety of any of the associated objects changing after setting the table to
parallel safe. The planner would have relied on the outdated parallel safety of
the table and chosen parallel inserts and the executor will catch such situations.
Looks like my understanding was wrong.Currently, we assume user is responsible for its correctness.
Because, from our research, when the parallel safety of some of these objects is changed,
it's costly to reflect it on the parallel safety of tables that depend on them.
(we need to scan the pg_depend,pg_inherit,pg_index.... to find the target table)
Agree.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Tue, Apr 27, 2021 at 7:45 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Apr 27, 2021 at 10:51 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I still feel that why we shouldn't limit the declarative approach to
only partitioned tables? And for normal tables, possibly with a
minimal cost(??), the server can do the safety checking. I know this
feels a little inconsistent. In the planner we will have different
paths like: if (partitioned_table) { check the parallel safety tag
associated with the table } else { perform the parallel safety of the
associated objects }.Personally I think the simplest and best approach is just do it
consistently, using the declarative approach across all table types.
Agree.
Then, running the pg_get_parallel_safety will have some overhead if
there are many partitions associated with a table. And, this is the
overhead planner would have had to incur without the declarative
approach which we are trying to avoid with this design.The big difference is that pg_get_parallel_safety() is intended to be
used during development, not as part of runtime parallel-safety checks
(which are avoided using the declarative approach).
So there is no runtime overhead associated with pg_get_parallel_safety().
Yes, while we avoid runtime overhead, but we run the risk of changed
parallel safety of any of the underlying objects/functions/partitions.
This risk will anyways be unavoidable with declarative approach.
I'm thinking that when users say ALTER TABLE partioned_table SET
PARALLEL TO 'safe';, we check all the partitions' and their associated
objects' parallel safety? If all are parallel safe, then only we set
partitioned_table as parallel safe. What should happen if the parallel
safety of any of the associated objects/partitions changes after
setting the partitioned_table safety?With the declarative approach, there is no parallel-safety checking on
either the CREATE/ALTER when the parallel-safety is declared/set.
It's up to the user to get it right. If it's actually wrong, it will
be detected at runtime.
As I said upthread, we can provide the parallel safety check of
associated objects/partitions as an option with default as false. I'm
not sure if this is a good thing to do at all. Thoughts?
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Tue, Apr 27, 2021 at 8:12 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Apr 27, 2021 at 7:45 AM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Tue, Apr 27, 2021 at 10:51 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:I still feel that why we shouldn't limit the declarative approach to
only partitioned tables? And for normal tables, possibly with a
minimal cost(??), the server can do the safety checking. I know this
feels a little inconsistent. In the planner we will have different
paths like: if (partitioned_table) { check the parallel safety tag
associated with the table } else { perform the parallel safety of the
associated objects }.Personally I think the simplest and best approach is just do it
consistently, using the declarative approach across all table types.Yeah, if we decide to go with a declarative approach then that sounds
like a better approach.
Agree.
Then, running the pg_get_parallel_safety will have some overhead if
there are many partitions associated with a table. And, this is the
overhead planner would have had to incur without the declarative
approach which we are trying to avoid with this design.The big difference is that pg_get_parallel_safety() is intended to be
used during development, not as part of runtime parallel-safety checks
(which are avoided using the declarative approach).
So there is no runtime overhead associated with pg_get_parallel_safety().I'm thinking that when users say ALTER TABLE partioned_table SET
PARALLEL TO 'safe';, we check all the partitions' and their associated
objects' parallel safety? If all are parallel safe, then only we set
partitioned_table as parallel safe. What should happen if the parallel
safety of any of the associated objects/partitions changes after
setting the partitioned_table safety?With the declarative approach, there is no parallel-safety checking on
either the CREATE/ALTER when the parallel-safety is declared/set.
It's up to the user to get it right. If it's actually wrong, it will
be detected at runtime.OTOH, even if we want to verify at DDL time, we won't be able to
maintain it at the later point of time say if user changed the
parallel-safety of some function used in check constraint. I think the
function pg_get_parallel_safety() will help the user to decide whether
it can declare table parallel-safe. Now, it is quite possible that the
user can later change the parallel-safe property of some function then
that should be caught at runtime.
Yeah, this is an unavoidable problem with the declarative approach.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
Hi,
Attaching new version patches with the following change:
0002:
1): Change "ALTER/CREATE TABLE PARALLEL SAFE" to "ALTER/CREATE TABLE PARALLEL DML SAFE"
2): disallow temp/foreign table to be parallel safe.
0003:
1) Temporarily, add the check of built-in function by adding a member for proparallel in FmgrBuiltin.
To avoid enlarging FmgrBuiltin struct , change the existing bool members strict and and retset into
one member of type char, and represent the original values with some bit flags.
Note: this will lock down the parallel property of built-in function, but, I think the parallel safety of built-in function
is related to the C code, user should not change the property of it unless they change its code. So, I think it might be
better to disallow changing parallel safety for built-in functions, Thoughts ?
I have not added the parallel safety check in ALTER/CREATE table PARALLEL DML SAFE command.
I think it seems better to add it after some more discussion.
Best regards,
houzj
Attachments:
v2-POC-0004-fix-testcase-with-wrong-parallel-safety-flag.patchapplication/octet-stream; name=v2-POC-0004-fix-testcase-with-wrong-parallel-safety-flag.patchDownload
From 6c22ae7398198bafeb09cfeb8735b88887e0a922 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 28 Apr 2021 10:25:14 +0800
Subject: [PATCH] fix-testcase-with-wrong-parallel-safety-flag
---
src/backend/snowball/snowball_func.sql.in | 4 ++--
src/include/catalog/pg_proc.dat | 8 ++++++++
src/pl/plpgsql/src/plpgsql--1.0.sql | 2 +-
src/test/isolation/specs/deadlock-parallel.spec | 4 ++--
src/test/regress/expected/aggregates.out | 1 +
src/test/regress/expected/create_type.out | 4 ++--
src/test/regress/expected/domain.out | 2 +-
src/test/regress/expected/insert.out | 2 +-
src/test/regress/input/create_function_1.source | 4 ++--
src/test/regress/output/create_function_1.source | 4 ++--
src/test/regress/sql/aggregates.sql | 1 +
src/test/regress/sql/create_type.sql | 4 ++--
src/test/regress/sql/domain.sql | 2 +-
src/test/regress/sql/insert.sql | 2 +-
14 files changed, 27 insertions(+), 17 deletions(-)
diff --git a/src/backend/snowball/snowball_func.sql.in b/src/backend/snowball/snowball_func.sql.in
index cb1eaca4fb..08bf3397e4 100644
--- a/src/backend/snowball/snowball_func.sql.in
+++ b/src/backend/snowball/snowball_func.sql.in
@@ -21,11 +21,11 @@ SET search_path = pg_catalog;
CREATE FUNCTION dsnowball_init(INTERNAL)
RETURNS INTERNAL AS '$libdir/dict_snowball', 'dsnowball_init'
-LANGUAGE C STRICT;
+LANGUAGE C STRICT PARALLEL SAFE;
CREATE FUNCTION dsnowball_lexize(INTERNAL, INTERNAL, INTERNAL, INTERNAL)
RETURNS INTERNAL AS '$libdir/dict_snowball', 'dsnowball_lexize'
-LANGUAGE C STRICT;
+LANGUAGE C STRICT PARALLEL SAFE;
CREATE TEXT SEARCH TEMPLATE snowball
(INIT = dsnowball_init,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cac5b82cc6..5690c66f07 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8485,6 +8485,14 @@
{ oid => '2892', descr => 'release all advisory locks',
proname => 'pg_advisory_unlock_all', provolatile => 'v', proparallel => 'r',
prorettype => 'void', proargtypes => '', prosrc => 'pg_advisory_unlock_all' },
+{ oid => '6123', descr => 'obtain shared advisory lock for testing purpose',
+ proname => 'pg_advisory_test_xact_lock_shared', provolatile => 'v',
+ prorettype => 'void', proargtypes => 'int8',
+ prosrc => 'pg_advisory_xact_lock_shared_int8' },
+{ oid => '6124', descr => 'obtain exclusive advisory lock for testing purpose',
+ proname => 'pg_advisory_test_xact_lock', provolatile => 'v',
+ prorettype => 'void', proargtypes => 'int8',
+ prosrc => 'pg_advisory_xact_lock_int8' },
# XML support
{ oid => '2893', descr => 'I/O',
diff --git a/src/pl/plpgsql/src/plpgsql--1.0.sql b/src/pl/plpgsql/src/plpgsql--1.0.sql
index 6e5b990fcc..165a670aa8 100644
--- a/src/pl/plpgsql/src/plpgsql--1.0.sql
+++ b/src/pl/plpgsql/src/plpgsql--1.0.sql
@@ -1,7 +1,7 @@
/* src/pl/plpgsql/src/plpgsql--1.0.sql */
CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler
- LANGUAGE c AS 'MODULE_PATHNAME';
+ LANGUAGE c PARALLEL SAFE AS 'MODULE_PATHNAME';
CREATE FUNCTION plpgsql_inline_handler(internal) RETURNS void
STRICT LANGUAGE c AS 'MODULE_PATHNAME';
diff --git a/src/test/isolation/specs/deadlock-parallel.spec b/src/test/isolation/specs/deadlock-parallel.spec
index 7ad290c0bd..7beaad46ee 100644
--- a/src/test/isolation/specs/deadlock-parallel.spec
+++ b/src/test/isolation/specs/deadlock-parallel.spec
@@ -37,10 +37,10 @@
setup
{
create function lock_share(int,int) returns int language sql as
- 'select pg_advisory_xact_lock_shared($1); select 1;' parallel safe;
+ 'select pg_advisory_test_xact_lock_shared($1); select 1;' parallel safe;
create function lock_excl(int,int) returns int language sql as
- 'select pg_advisory_xact_lock($1); select 1;' parallel safe;
+ 'select pg_advisory_test_xact_lock($1); select 1;' parallel safe;
create table bigt as select x from generate_series(1, 10000) x;
analyze bigt;
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index ca06d41dd0..2a4a83fab7 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -2386,6 +2386,7 @@ rollback;
BEGIN;
CREATE FUNCTION balkifnull(int8, int4)
RETURNS int8
+PARALLEL SAFE
STRICT
LANGUAGE plpgsql AS $$
BEGIN
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 14394cc95c..eb1c6bdcd2 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -48,7 +48,7 @@ NOTICE: return type int42 is only a shell
CREATE FUNCTION int42_out(int42)
RETURNS cstring
AS 'int4out'
- LANGUAGE internal STRICT IMMUTABLE;
+ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE;
NOTICE: argument type int42 is only a shell
CREATE FUNCTION text_w_default_in(cstring)
RETURNS text_w_default
@@ -58,7 +58,7 @@ NOTICE: return type text_w_default is only a shell
CREATE FUNCTION text_w_default_out(text_w_default)
RETURNS cstring
AS 'textout'
- LANGUAGE internal STRICT IMMUTABLE;
+ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE;
NOTICE: argument type text_w_default is only a shell
CREATE TYPE int42 (
internallength = 4,
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..c82d189823 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -1067,7 +1067,7 @@ drop domain di;
-- this has caused issues in the past
--
create function sql_is_distinct_from(anyelement, anyelement)
-returns boolean language sql
+returns boolean language sql parallel safe
as 'select $1 is distinct from $2 limit 1';
create domain inotnull int
check (sql_is_distinct_from(value, null));
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..7e7ef24098 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -415,7 +415,7 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
create or replace function part_hashint4_noop(value int4, seed int8)
returns int8 as $$
select value + seed;
-$$ language sql immutable;
+$$ language sql immutable parallel safe;
create operator class part_test_int4_ops
for type int4
using hash as
diff --git a/src/test/regress/input/create_function_1.source b/src/test/regress/input/create_function_1.source
index 6c69b7fe6c..b9a4e7af38 100644
--- a/src/test/regress/input/create_function_1.source
+++ b/src/test/regress/input/create_function_1.source
@@ -10,7 +10,7 @@ CREATE FUNCTION widget_in(cstring)
CREATE FUNCTION widget_out(widget)
RETURNS cstring
AS '@libdir@/regress@DLSUFFIX@'
- LANGUAGE C STRICT IMMUTABLE;
+ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
CREATE FUNCTION int44in(cstring)
RETURNS city_budget
@@ -20,7 +20,7 @@ CREATE FUNCTION int44in(cstring)
CREATE FUNCTION int44out(city_budget)
RETURNS cstring
AS '@libdir@/regress@DLSUFFIX@'
- LANGUAGE C STRICT IMMUTABLE;
+ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
CREATE FUNCTION check_primary_key ()
RETURNS trigger
diff --git a/src/test/regress/output/create_function_1.source b/src/test/regress/output/create_function_1.source
index c66146db9d..0c1390e8c5 100644
--- a/src/test/regress/output/create_function_1.source
+++ b/src/test/regress/output/create_function_1.source
@@ -10,7 +10,7 @@ DETAIL: Creating a shell type definition.
CREATE FUNCTION widget_out(widget)
RETURNS cstring
AS '@libdir@/regress@DLSUFFIX@'
- LANGUAGE C STRICT IMMUTABLE;
+ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
NOTICE: argument type widget is only a shell
CREATE FUNCTION int44in(cstring)
RETURNS city_budget
@@ -21,7 +21,7 @@ DETAIL: Creating a shell type definition.
CREATE FUNCTION int44out(city_budget)
RETURNS cstring
AS '@libdir@/regress@DLSUFFIX@'
- LANGUAGE C STRICT IMMUTABLE;
+ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
NOTICE: argument type city_budget is only a shell
CREATE FUNCTION check_primary_key ()
RETURNS trigger
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index eb80a2fe06..68990b5b5f 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -978,6 +978,7 @@ rollback;
BEGIN;
CREATE FUNCTION balkifnull(int8, int4)
RETURNS int8
+PARALLEL SAFE
STRICT
LANGUAGE plpgsql AS $$
BEGIN
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index a32a9e6795..285707e532 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -51,7 +51,7 @@ CREATE FUNCTION int42_in(cstring)
CREATE FUNCTION int42_out(int42)
RETURNS cstring
AS 'int4out'
- LANGUAGE internal STRICT IMMUTABLE;
+ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE;
CREATE FUNCTION text_w_default_in(cstring)
RETURNS text_w_default
AS 'textin'
@@ -59,7 +59,7 @@ CREATE FUNCTION text_w_default_in(cstring)
CREATE FUNCTION text_w_default_out(text_w_default)
RETURNS cstring
AS 'textout'
- LANGUAGE internal STRICT IMMUTABLE;
+ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE;
CREATE TYPE int42 (
internallength = 4,
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 549c0b5adf..a022ae4223 100644
--- a/src/test/regress/sql/domain.sql
+++ b/src/test/regress/sql/domain.sql
@@ -724,7 +724,7 @@ drop domain di;
--
create function sql_is_distinct_from(anyelement, anyelement)
-returns boolean language sql
+returns boolean language sql parallel safe
as 'select $1 is distinct from $2 limit 1';
create domain inotnull int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index bfaa8a3b27..895524bc88 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -258,7 +258,7 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
create or replace function part_hashint4_noop(value int4, seed int8)
returns int8 as $$
select value + seed;
-$$ language sql immutable;
+$$ language sql immutable parallel safe;
create operator class part_test_int4_ops
for type int4
--
2.18.4
v2-POC-0001-get-parallel-safety-function.patchapplication/octet-stream; name=v2-POC-0001-get-parallel-safety-function.patchDownload
From 202024c8d24ac9fa393b8e3b36bfdcc14d7bae0e Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 21 Apr 2021 19:21:11 +0800
Subject: [PATCH] get-parallel-safety-function
provide a utility function pg_get_parallel_safety('table_name') that returns records of
(objid, classid, parallel_safety) that represent the parallel safety of objects that
determine the parallel safety of the specified table. The user can use this function
to identify problematic objects when a parallel DML fails or is not parallelized in an
expected manner.
When detecting an parallel unsafe/restricted function in index(or others can have an expression),
return both the function oid and the index oid.
---
src/backend/optimizer/plan/planner.c | 3 +-
src/backend/optimizer/util/clauses.c | 600 ++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 75 ++++
src/backend/utils/cache/typcache.c | 14 +
src/include/access/xact.h | 14 +
src/include/catalog/pg_proc.dat | 18 +-
src/include/optimizer/clauses.h | 9 +
src/include/utils/typcache.h | 2 +
8 files changed, 729 insertions(+), 6 deletions(-)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..dbc2827d20 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d9ad4efc5e..3ec0bf26cb 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,13 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -43,6 +50,9 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +61,8 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all;
+ List *func_oids;
+ PartitionDirectory partition_directory;
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -98,6 +113,20 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static List *target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context);
+static List *target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *index_expr_max_parallel_hazard(Relation index_rel, List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting, max_parallel_hazard_context *context);
+static List *target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static List *target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -149,6 +178,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
* Aggregate-function clause manipulation
@@ -620,6 +650,10 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
+
(void) max_parallel_hazard_walker((Node *) parse, &context);
return context.max_hazard;
}
@@ -651,6 +685,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -682,7 +719,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -699,6 +736,63 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+static bool
+parallel_safety_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ bool max_hazard_found;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+ max_hazard_found = max_parallel_hazard_test(proparallel, cont);
+
+ if ((proparallel != PROPARALLEL_SAFE && cont->check_all) ||
+ max_hazard_found)
+ {
+ cont->func_oids = lappend(cont->func_oids,
+ make_safety_object(func_id, ProcedureRelationId, proparallel));
+ }
+
+ return max_hazard_found && !cont->check_all;
+}
+
+/* Check parallel unsafe/restricted function in expression */
+static bool
+parallel_safety_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_safety_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ if (target_rel_domain_max_parallel_hazard(((CoerceToDomain *)node)->resulttype, context) != NIL &&
+ !context->check_all)
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_safety_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -854,6 +948,510 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+List*
+target_rel_max_parallel_hazard(RangeVar *relrv, bool findall, char max_interesting)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+ List *objects;
+
+ context.check_all = findall;
+ context.func_oids = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_openrv(relrv, AccessShareLock);
+
+ objects = target_rel_all_parallel_hazard_recurse(targetRel, &context);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ return objects;
+}
+
+
+List *
+target_rel_all_parallel_hazard_recurse(Relation rel, max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ Assert(context != NULL && context->check_all);
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context);
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(rel->rd_rel->oid, RelationRelationId, PROPARALLEL_RESTRICTED));
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ (void) target_rel_partitions_max_parallel_hazard(rel, context);
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ (void) target_rel_index_max_parallel_hazard(rel, context);
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ (void) target_rel_trigger_max_parallel_hazard(rel, context);
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef)
+ {
+ Node *defaultexpr;
+ defaultexpr = build_column_default(rel, attnum);
+ parallel_safety_walker((Node *) defaultexpr, context);
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ (void) target_rel_domain_max_parallel_hazard(att->atttypid, context);
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ (void) target_rel_chk_constr_max_parallel_hazard(rel, context);
+
+ return context->func_oids;
+}
+
+/*
+ * target_rel_trigger_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified relation's
+ * trigger data.
+ */
+static List*
+target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ Assert(context != NULL && context->check_all);
+
+ if (rel->trigdesc == NULL)
+ return context->func_oids;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgfoid, ProcedureRelationId, proparallel));
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgoid, TriggerRelationId, proparallel));
+ }
+ }
+
+ return context->func_oids;
+}
+
+static List*
+index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ Form_pg_index indexStruct;
+ int i;
+ ListCell *index_expr_item;
+
+ Assert(context != NULL && context->check_all);
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ if (ii_Expressions != NIL)
+ {
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ /* find some not safe objects */
+ parallel_safety_walker(index_expr, context);
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+ }
+
+ if (ii_Predicate != NIL)
+ parallel_safety_walker((Node *) ii_Predicate, context);
+
+ /*
+ * Check parallel-safety of any index AM support functions.
+ */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+ if (nsupport > 0)
+ {
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+
+ Oid funcOid = index_rel->rd_support[i];
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+ if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ }
+ }
+
+ return context->func_oids;
+}
+
+/*
+ * target_rel_index_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any existing index
+ * expressions or index predicate of a specified relation.
+ */
+static List*
+target_rel_index_max_parallel_hazard(Relation rel, max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+
+ Assert(context != NULL && context->check_all);
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ index_expr_max_parallel_hazard(index_rel, ii_Expressions,
+ ii_Predicate, context->check_all,
+ context->max_interesting,
+ context);
+
+ /* Add the index itself to the objects list */
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(index_oid, IndexRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ index_close(index_rel, lockmode);
+ }
+
+ list_free(index_oid_list);
+
+ return context->func_oids;
+}
+
+/*
+ * target_rel_domain_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified DOMAIN type.
+ * Only any CHECK expressions are examined for parallel-safety.
+ */
+static List*
+target_rel_domain_max_parallel_hazard(Oid typid, max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+
+ Assert(context != NULL && context->check_all);
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) r->check_expr, context);
+
+ /* Add the Constraint itself to the objects list */
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_domain_constraint_oid(typid, r->name, false),
+ ConstraintRelationId,
+ context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return context->func_oids;
+
+}
+
+/*
+ * target_rel_partitions_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any partitions of a
+ * of a specified relation.
+ */
+static List*
+target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs, *qual;
+
+ Assert(context != NULL && context->check_all);
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ parallel_safety_walker((Node *) qual, context);
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return context->func_oids;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+ max_parallel_hazard_test(proparallel, context);
+
+ if (proparallel != PROPARALLEL_SAFE)
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ parallel_safety_walker(check_expr, context);
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ (void) target_rel_all_parallel_hazard_recurse(part_rel, context);
+ table_close(part_rel, AccessShareLock);
+ }
+
+ return context->func_oids;
+}
+
+/*
+ * target_rel_chk_constr_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any CHECK expressions or
+ * CHECK constraints related to the specified relation.
+ */
+static List*
+target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ List *temp_objects;
+
+ Assert(context != NULL && context->check_all);
+
+ tupdesc = RelationGetDescr(rel);
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ if (tupdesc->constr != NULL && tupdesc->constr->num_check > 0)
+ {
+ int i;
+
+ ConstrCheck *check = tupdesc->constr->check;
+
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) check_expr, context);
+
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_relation_constraint_oid(rel->rd_rel->oid, check->ccname, true), ConstraintRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+ }
+
+ return context->func_oids;
+}
+
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4dfd7..33bc6b602d 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,77 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ RangeVar *relvar;
+ text *relname_text;
+ ReturnSetInfo *rsinfo;
+
+ relname_text = PG_GETARG_TEXT_PP(0);
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+
+ MemoryContextSwitchTo(oldcontext);
+
+ relvar = makeRangeVarFromNameList(textToQualifiedNameList(relname_text));
+ objects = target_rel_max_parallel_hazard(relvar, true, PROPARALLEL_UNSAFE);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 4915ef5934..260f5d45c8 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,20 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+
+List *GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index f49a57b35e..c04e6a98d7 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -467,4 +467,18 @@ extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
+
#endif /* XACT_H */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b62abcd22c..a511a03021 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3765,6 +3765,16 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'information about the parallel unsafe or restricted objects in the target table',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3772,11 +3782,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887a85..857d89e0d4 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -52,5 +59,7 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_max_parallel_hazard(RangeVar *relrv, bool findall, char max_interesting);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a4b7..28ca7d8a6e 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.18.4
v2-POC-0002-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v2-POC-0002-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 443246db38c9c3e3547d043c54b658b17d65e8fd Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 27 Apr 2021 17:42:24 +0800
Subject: [PATCH] CREATE/ALTER TABLE PARALLEL DML
Enabling users to declare that the table allows parallel data modification,
Add a table property that represents parallel safety of the table for DML
statement execution. Users specify it as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u', 'r', or 's',
just like pg_proc's proparallel. The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have the specified parallel safety or safer one.
The user is responsible for its correctness. If the parallel processes
find an object that is less safer than the assumed parallel safety during
statement execution, it throws an ERROR and abort the statement execution.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 +++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 25 +++++-
src/backend/parser/gram.y | 64 ++++++++++----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 ++++++++--
src/bin/pg_dump/pg_dump.h | 1 +
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 238 insertions(+), 33 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..88fcd57082 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 42ff175bc8..3bf4d9eff5 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -301,6 +301,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -403,7 +404,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -960,6 +962,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1153,6 +1156,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1300,6 +1304,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a628b3281c..6ba64d8dbb 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 933a0734d1..e4a05205be 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..2151121066 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce882012e..45aacc8b7a 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9ccb..6f25c231e9 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7d00f4eb25..ca4528eb06 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -933,6 +936,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support parallel data modification on foreign or temporary table")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -951,6 +976,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4206,6 +4232,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4748,6 +4775,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5155,6 +5187,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
Assert(rel->rd_rel->relkind == RELKIND_INDEX);
ATExecAlterCollationRefreshVersion(rel, cmd->object);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18499,3 +18534,55 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal((Value *) def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support parallel data modification on foreign or temporary table")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 036fa69d17..ec3834fae9 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..2d77a88726 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 632cc31a04..cb07c93f21 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3521,6 +3521,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a410a29a17..b527c6ed0b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1276,6 +1277,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c723f6d635..b0f4ea9a2e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1106,6 +1106,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2702,6 +2703,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3746668f52..16ddc6699e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 3ec0bf26cb..93344a6b1a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -177,7 +177,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
-
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -645,6 +645,7 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
@@ -654,7 +655,27 @@ max_parallel_hazard(Query *parse)
context.func_oids = NIL;
context.partition_directory = NULL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b4ab4014c8..841db357a0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -610,6 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> hash_partbound_elem
%type <str> optColumnCompression
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +655,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2690,6 +2691,14 @@ alter_table_cmd:
n->object = $3;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL SAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3275,7 +3284,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3289,12 +3298,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3308,12 +3318,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3328,12 +3339,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3348,12 +3360,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3368,12 +3382,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3388,6 +3404,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4086,6 +4103,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4233,7 +4254,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4242,6 +4263,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5021,7 +5043,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5033,15 +5055,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5053,15 +5076,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5074,15 +5098,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5095,10 +5120,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15553,6 +15579,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16093,6 +16120,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 29702d6eab..9b8d6a049c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1868,6 +1868,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3337,7 +3338,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3487,6 +3489,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3cb3598f2b..5eb19ca07d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6291,6 +6291,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relproparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6395,7 +6396,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6487,7 +6488,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6540,7 +6541,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6593,7 +6594,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6646,7 +6647,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6697,7 +6698,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6745,7 +6746,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6793,7 +6794,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6840,7 +6841,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6909,6 +6910,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relproparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6964,6 +6966,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relproparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16542,6 +16545,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5340843081..c85113c8ed 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..b59975919b 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729436..af280b5095 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 615dfa26aa..2b3fd010d8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1934,7 +1934,8 @@ typedef enum AlterTableType
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2159,6 +2160,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d847..6b532b034a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..05222faccd 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 2fcdf79323..aa970375ae 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -106,7 +106,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5438..e1f5678eef 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.18.4
v2-POC-0003-check-parallel-safety-in-fmgr_info.patchapplication/octet-stream; name=v2-POC-0003-check-parallel-safety-in-fmgr_info.patchDownload
From f71d688407beea5a56a946c86c41b20eef5b8781 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 27 Apr 2021 19:25:01 +0800
Subject: [PATCH] check-parallel-safety-in-fmgr_info
1)
Add parallel safety to FmgrBuiltin. To avoid enlarging the size of FmgrBuiltin array,
combining strict,retset,parallel into one bitmask var.
2)
Check function's parallel safety in fmgr_info_cxt_security.
3)
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +
src/backend/executor/execMain.c | 3 +
src/backend/optimizer/plan/planner.c | 18 +-
src/backend/utils/Gen_fmgrtab.pl | 36 +-
src/backend/utils/fmgr/fmgr.c | 27 +-
src/include/access/xact.h | 1 +
src/include/utils/fmgrtab.h | 13 +-
src/test/regress/expected/insert_parallel.out | 530 ++++++++++++++++++
src/test/regress/parallel_schedule | 1 +
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 337 +++++++++++
11 files changed, 967 insertions(+), 26 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 441445927e..2d68e4633a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1014,6 +1014,32 @@ IsInParallelMode(void)
return CurrentTransactionState->parallelModeLevel != 0;
}
+/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
/*
* CommandCounterIncrement
*/
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index df3d7f9a8b..b7bd95f852 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index dbc2827d20..7736813230 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
diff --git a/src/backend/utils/Gen_fmgrtab.pl b/src/backend/utils/Gen_fmgrtab.pl
index 881568defd..7769230734 100644
--- a/src/backend/utils/Gen_fmgrtab.pl
+++ b/src/backend/utils/Gen_fmgrtab.pl
@@ -72,15 +72,16 @@ foreach my $row (@{ $catalog_data{pg_proc} })
push @fmgr,
{
- oid => $bki_values{oid},
- name => $bki_values{proname},
- lang => $bki_values{prolang},
- kind => $bki_values{prokind},
- strict => $bki_values{proisstrict},
- retset => $bki_values{proretset},
- nargs => $bki_values{pronargs},
- args => $bki_values{proargtypes},
- prosrc => $bki_values{prosrc},
+ oid => $bki_values{oid},
+ name => $bki_values{proname},
+ lang => $bki_values{prolang},
+ kind => $bki_values{prokind},
+ strict => $bki_values{proisstrict},
+ parallel => $bki_values{proparallel},
+ retset => $bki_values{proretset},
+ nargs => $bki_values{pronargs},
+ args => $bki_values{proargtypes},
+ prosrc => $bki_values{prosrc},
};
# Count so that we can detect overloaded pronames.
@@ -208,9 +209,16 @@ foreach my $s (sort { $a->{oid} <=> $b->{oid} } @fmgr)
# Create the fmgr_builtins table, collect data for fmgr_builtin_oid_index
print $tfh "\nconst FmgrBuiltin fmgr_builtins[] = {\n";
-my %bmap;
-$bmap{'t'} = 'true';
-$bmap{'f'} = 'false';
+my %bmap_strict;
+$bmap_strict{'t'} = 1 << 0;
+$bmap_strict{'f'} = 0;
+my %bmap_retset;
+$bmap_retset{'t'} = 1 << 1;
+$bmap_retset{'f'} = 0;
+my %bmap_parallel;
+$bmap_parallel{'s'} = 1 << 2;
+$bmap_parallel{'r'} = 1 << 3;
+$bmap_parallel{'u'} = 1 << 4;
my @fmgr_builtin_oid_index;
my $last_builtin_oid = 0;
my $fmgr_count = 0;
@@ -220,9 +228,11 @@ foreach my $s (sort { $a->{oid} <=> $b->{oid} } @fmgr)
# We do not need entries for aggregate functions
next if $s->{kind} eq 'a';
+ my $bitflag = $bmap_strict{$s->{strict}} | $bmap_retset{$s->{retset}} | $bmap_parallel{$s->{parallel}};
print $tfh ",\n" if ($fmgr_count > 0);
print $tfh
- " { $s->{oid}, $s->{nargs}, $bmap{$s->{strict}}, $bmap{$s->{retset}}, \"$s->{prosrc}\", $s->{prosrc} }";
+# " { $s->{oid}, $s->{nargs}, $bmap{$s->{strict}}, $bmap{$s->{retset}}, \"$s->{prosrc}\", $s->{prosrc} }";
+ " { $s->{oid}, $s->{nargs}, $bitflag, \"$s->{prosrc}\", $s->{prosrc} }";
$fmgr_builtin_oid_index[ $s->{oid} ] = $fmgr_count++;
$last_builtin_oid = $s->{oid};
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 3dfe6e5825..4b78578595 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -16,6 +16,8 @@
#include "postgres.h"
#include "access/detoast.h"
+#include "access/parallel.h"
+#include "access/xact.h"
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
@@ -56,6 +58,7 @@ static HTAB *CFuncHash = NULL;
static void fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
bool ignore_security);
+static void fmgr_check_parallel_safety(char parallel_safety, Oid functionId);
static void fmgr_info_C_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple);
static void fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple);
static CFuncHashTabEntry *lookup_C_func(HeapTuple procedureTuple);
@@ -165,12 +168,15 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
if ((fbp = fmgr_isbuiltin(functionId)) != NULL)
{
+ /* Check parallel safety for built-in functions */
+ fmgr_check_parallel_safety(GETPARALLEL(fbp), functionId);
+
/*
* Fast path for builtin functions: don't bother consulting pg_proc
*/
finfo->fn_nargs = fbp->nargs;
- finfo->fn_strict = fbp->strict;
- finfo->fn_retset = fbp->retset;
+ finfo->fn_strict = GETSTRICT(fbp);
+ finfo->fn_retset = GETRETSET(fbp);
finfo->fn_stats = TRACK_FUNC_ALL; /* ie, never track */
finfo->fn_addr = fbp->func;
finfo->fn_oid = functionId;
@@ -183,6 +189,9 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
elog(ERROR, "cache lookup failed for function %u", functionId);
procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
+ /* Check parallel safety for other functions */
+ fmgr_check_parallel_safety(procedureStruct->proparallel, functionId);
+
finfo->fn_nargs = procedureStruct->pronargs;
finfo->fn_strict = procedureStruct->proisstrict;
finfo->fn_retset = procedureStruct->proretset;
@@ -264,6 +273,20 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
ReleaseSysCache(procedureTuple);
}
+static void
+fmgr_check_parallel_safety(char parallel_safety, Oid functionId)
+{
+ if (IsInParallelMode() &&
+ ((IsParallelWorker() &&
+ parallel_safety == PROPARALLEL_RESTRICTED) ||
+ parallel_safety == PROPARALLEL_UNSAFE))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+ errmsg("parallel-safety execution violation of function \"%s\" (%c)",
+ get_func_name(functionId), parallel_safety)));
+}
+
+
/*
* Return module and C function name providing implementation of functionId.
*
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index c04e6a98d7..34cfaf542c 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,6 +466,7 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
/*
* IsModifySupportedInParallelMode
diff --git a/src/include/utils/fmgrtab.h b/src/include/utils/fmgrtab.h
index 21a5f21156..a8e1a72916 100644
--- a/src/include/utils/fmgrtab.h
+++ b/src/include/utils/fmgrtab.h
@@ -26,12 +26,21 @@ typedef struct
{
Oid foid; /* OID of the function */
short nargs; /* 0..FUNC_MAX_ARGS, or -1 if variable count */
- bool strict; /* T if function is "strict" */
- bool retset; /* T if function returns a set */
+ char bitflag; /* 1 << 0 if function is "strict"
+ * 1 << 1 if function returns a set
+ * 1 << 2 if function parallel safe
+ * 1 << 3 if function parallel restricted
+ * 1 << 4 if function parallel unsafe */
const char *funcName; /* C name of the function */
PGFunction func; /* pointer to compiled function */
} FmgrBuiltin;
+#define GETSTRICT(fbp) ((fbp->bitflag & (1 << 0)) ? true : false)
+#define GETRETSET(fbp) ((fbp->bitflag & (1 << 1)) ? true : false)
+#define GETPARALLEL(fbp) ((fbp->bitflag & (1 << 2)) ? PROPARALLEL_SAFE \
+ : ((fbp->bitflag & (1 << 3)) ? PROPARALLEL_RESTRICTED \
+ : PROPARALLEL_UNSAFE))
+
extern const FmgrBuiltin fmgr_builtins[];
extern const int fmgr_nbuiltins; /* number of entries in table */
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..12ca304953
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,530 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+--
+-- END: setup some tables and data needed by the tests.
+--
+-- 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
+);
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+alter table names2 parallel dml safe;
+insert into names2 select * from names returning *;
+ERROR: parallel-safety execution violation of function "fullname_parallel_unsafe" (u)
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+--
+-- Test INSERT with underlying 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 returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+insert into table_check_b select * from names;
+ERROR: parallel-safety execution violation of function "check_b_unsafe" (u)
+--
+-- Test table with parallel-safe after stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_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_after_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_after_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_after_trigger_safe
+--
+-- Test table with parallel-unsafe after stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+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_after_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_after_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+insert into names_with_unsafe_trigger select * from names;
+ERROR: parallel-safety execution violation of function "insert_after_trigger_unsafe" (u)
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_after_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_after_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+insert into names_with_unsafe_trigger select * from names;
+ERROR: parallel-safety execution violation of function "insert_after_trigger_unsafe" (u)
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a091300857..c6741a98aa 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -95,6 +95,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 5644847601..638b7a23d0 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -151,6 +151,7 @@ test: stats_ext
test: collate.linux.utf8
test: select_parallel
test: write_parallel
+test: insert_parallel
test: publication
test: subscription
test: select_views
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..764966611d
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,337 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+-- 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
+);
+
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+alter table names2 parallel dml safe;
+insert into names2 select * from names returning *;
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+insert into table_check_b select * from names;
+
+--
+-- Test table with parallel-safe after stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_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_after_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_after_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe after stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+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_after_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_after_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_after_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_after_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
On Wed, Apr 28, 2021 at 12:44 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
0003:
1) Temporarily, add the check of built-in function by adding a member for proparallel in FmgrBuiltin.
To avoid enlarging FmgrBuiltin struct , change the existing bool members strict and and retset into
one member of type char, and represent the original values with some bit flags.
I was thinking that it would be better to replace the two bool members
with one "unsigned char" member for the bitflags for strict and
retset, and another "char" member for parallel.
The struct would still remain the same size as it originally was, and
you wouldn't need to convert between bit settings and char
('u'/'r'/'s') each time a built-in function was checked for
parallel-safety in fmgr_info().
Note: this will lock down the parallel property of built-in function, but, I think the parallel safety of built-in function
is related to the C code, user should not change the property of it unless they change its code. So, I think it might be
better to disallow changing parallel safety for built-in functions, Thoughts ?
I'd vote for disallowing it (unless someone can justify why it
currently is allowed).
I have not added the parallel safety check in ALTER/CREATE table PARALLEL DML SAFE command.
I think it seems better to add it after some more discussion.
I'd vote for not adding such a check (as this is a declaration).
Some additional comments:
1) In patch 0002 comment, it says:
This property is recorded in pg_class's relparallel column as 'u', 'r', or 's',
just like pg_proc's proparallel. The default is UNSAFE.
It should say "relparalleldml" column.
2) With the patches applied, I seem to be getting a couple of errors
when running "make installcheck-world" with
force_parallel_mode=regress in effect.
Can you please try it?
Regards,
Greg Nancarrow
Fujitsu Australia
On Mon, Apr 12, 2021 at 6:52 AM tsunakawa.takay@fujitsu.com
<tsunakawa.takay@fujitsu.com> wrote:
SOLUTION TO (1)
========================================The candidate ideas are:
1) Caching the result of parallel-safety check
The planner stores the result of checking parallel safety for each relation in relcache, or some purpose-built hash table in shared memory.The problems are:
* Even if the target relation turns out to be parallel safe by looking at those data structures, we cannot assume it remains true until the SQL statement finishes. For instance, other sessions might add a parallel-unsafe index to its descendant relations. Other examples include that when the user changes the parallel safety of indexes or triggers by running ALTER FUNCTION on the underlying index AM function or trigger function, the relcache entry of the table or index is not invalidated, so the correct parallel safety is not maintained in the cache.
In that case, when the executor encounters a parallel-unsafe object, it can change the cached state as being parallel-unsafe and error out.* Can't ensure fast access. With relcache, the first access in each session has to undergo the overhead of parallel-safety check. With a hash table in shared memory, the number of relations stored there would be limited, so the first access after database startup or the hash table entry eviction similarly experiences slowness.
* With a new hash table, some lwlock for concurrent access must be added, which can have an adverse effect on performance.
2) Enabling users to declare that the table allows parallel data modification
Add a table property that represents parallel safety of the table for DML statement execution. Users specify it as follows:CREATE TABLE table_name (...) PARALLEL { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL { UNSAFE | RESTRICTED | SAFE };This property is recorded in pg_class's relparallel column as 'u', 'r', or 's', just like pg_proc's proparallel. The default is UNSAFE.
So, in short, if we need to go with any sort of solution with caching,
we can't avoid
(a) locking all the partitions
(b) getting an error while execution because at a later point user has
altered the parallel-safe property of a relation.
We can't avoid locking all the partitions because while we are
executing the statement, the user can change the parallel-safety for
one of the partitions by changing a particular partition and if we
didn't have a lock on that partition, it will lead to an error during
execution. Now, here, one option could be that we document this point
and then don't take lock on any of the partitions except for root
table. So, the design would be simpler, that we either cache the
parallel-safe in relcache or shared hash table and just lock the
parent table and perform all parallel-safety checks for the first
time.
I think if we want to go with the behavior that we will error out
during statement execution if any parallel-safe property is changed at
run-time, it is better to go with the declarative approach. In the
declarative approach, at least the user will be responsible for taking
any such decision and the chances of toggling the parallel-safe
property will be less. To aid users, as suggested, we can provide a
function to determine parallel-safety of relation for DML operations.
Now, in the declarative approach, we can either go with whatever the
user has mentioned or we can do some checks during DDL to determine
the actual parallel-safety. I think even if try to determine
parallel-safety during DDL it will be quite tricky in some cases, like
when a user performs Alter Function to change parallel-safety of the
function used in some constraint for the table or if the user changes
parallel-safety of one of the partition then we need to traverse the
partition hierarchy upwards which doesn't seem advisable. So, I guess
it is better to go with whatever the user has mentioned but if you or
others feel we can have some sort of parallel-safety checks during DDL
as well.
The planner assumes that all of the table, its descendant partitions, and their ancillary objects have the specified parallel safety or safer one. The user is responsible for its correctness. If the parallel processes find an object that is less safer than the assumed parallel safety during statement execution, it throws an ERROR and abort the statement execution.
The objects that relate to the parallel safety of a DML target table are as follows:
* Column default expression
* DOMAIN type CHECK expression
* CHECK constraints on column
* Partition key
* Partition key support function
* Index expression
* Index predicate
* Index AM function
* Operator function
* Trigger functionWhen the parallel safety of some of these objects is changed, it's costly to reflect it on the parallel safety of tables that depend on them. So, we don't do it. Instead, we provide a utility function pg_get_parallel_safety('table_name') that returns records of (objid, classid, parallel_safety) that represent the parallel safety of objects that determine the parallel safety of the specified table. The function only outputs objects that are not parallel safe.
So, users need to check count(*) for this to determine
parallel-safety? How about if we provide a wrapper function on top of
this function or a separate function that returns char to indicate
whether it is safe, unsafe, or restricted to perform a DML operation
on the table?
How does the executor detect parallel unsafe objects? There are two ways:
1) At loading time
When the executor loads the definition of objects (tables, constraints, index, triggers, etc.) during the first access to them after session start or their eviction by sinval message, it checks the parallel safety.This is a legitimate way, but may need much code. Also, it might overlook necessary code changes without careful inspection.
If we want to go with a declarative approach, then I think we should
try to do this because it will be almost free in some cases and we can
detect error early. For example, when we decide to insert in a
partition that is declared unsafe whereas the root (partitioned) table
is declared safe.
--
With Regards,
Amit Kapila.
On Mon, May 10, 2021 at 11:21 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
Now, in the declarative approach, we can either go with whatever the
user has mentioned or we can do some checks during DDL to determine
the actual parallel-safety. I think even if try to determine
parallel-safety during DDL it will be quite tricky in some cases, like
when a user performs Alter Function to change parallel-safety of the
function used in some constraint for the table or if the user changes
parallel-safety of one of the partition then we need to traverse the
partition hierarchy upwards which doesn't seem advisable. So, I guess
it is better to go with whatever the user has mentioned but if you or
others feel we can have some sort of parallel-safety checks during DDL
as well.
IMHO, it makes sense to go with what the user has declared to avoid
complexity. And, I don't see any problem with that.
The planner assumes that all of the table, its descendant partitions, and their ancillary objects have the specified parallel safety or safer one. The user is responsible for its correctness. If the parallel processes find an object that is less safer than the assumed parallel safety during statement execution, it throws an ERROR and abort the statement execution.
The objects that relate to the parallel safety of a DML target table are as follows:
* Column default expression
* DOMAIN type CHECK expression
* CHECK constraints on column
* Partition key
* Partition key support function
* Index expression
* Index predicate
* Index AM function
* Operator function
* Trigger functionWhen the parallel safety of some of these objects is changed, it's costly to reflect it on the parallel safety of tables that depend on them. So, we don't do it. Instead, we provide a utility function pg_get_parallel_safety('table_name') that returns records of (objid, classid, parallel_safety) that represent the parallel safety of objects that determine the parallel safety of the specified table. The function only outputs objects that are not parallel safe.
So, users need to check count(*) for this to determine
parallel-safety? How about if we provide a wrapper function on top of
this function or a separate function that returns char to indicate
whether it is safe, unsafe, or restricted to perform a DML operation
on the table?
Such wrapper function make sense.
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
When the parallel safety of some of these objects is changed, it's costly to
reflect it on the parallel safety of tables that depend on them. So, we don't do
it. Instead, we provide a utility function pg_get_parallel_safety('table_name')
that returns records of (objid, classid, parallel_safety) that represent the
parallel safety of objects that determine the parallel safety of the specified
table. The function only outputs objects that are not parallel safe.So, users need to check count(*) for this to determine
parallel-safety? How about if we provide a wrapper function on top of
this function or a separate function that returns char to indicate
whether it is safe, unsafe, or restricted to perform a DML operation
on the table?Such wrapper function make sense.
Thanks for the suggestion, and I agree.
I will add another wrapper function and post new version patches soon.
Best regards,
houzj
So, users need to check count(*) for this to determine
parallel-safety? How about if we provide a wrapper function on top
of this function or a separate function that returns char to
indicate whether it is safe, unsafe, or restricted to perform a DML
operation on the table?Such wrapper function make sense.
Thanks for the suggestion, and I agree.
I will add another wrapper function and post new version patches soon.
Attaching new version patches with the following changes:
0001
Add a new function pg_get_max_parallel_hazard('table_name') returns char('s', 'u', 'r')
which indicate whether it is safe, unsafe, or restricted to perform a DML.
0003
Temporarily, I removed the safety check for function in the executor.
Because we are trying to post the safety check as a separate patch which
can help detect parallel unsafe function in parallel mode, and the approach
is still in discussion[1]/messages/by-id/OS0PR01MB571646637784DAF1DD4C8BE994539@OS0PR01MB5716.jpnprd01.prod.outlook.com.
Comments and suggestions are welcome either in that thread[1]/messages/by-id/OS0PR01MB571646637784DAF1DD4C8BE994539@OS0PR01MB5716.jpnprd01.prod.outlook.com or here.
[1]: /messages/by-id/OS0PR01MB571646637784DAF1DD4C8BE994539@OS0PR01MB5716.jpnprd01.prod.outlook.com
Best regards,
houzj
Attachments:
v3-POC-0003-xid-related-and-testcase.patchapplication/octet-stream; name=v3-POC-0003-xid-related-and-testcase.patchDownload
From 5cf349126793d555e965935a128c2878f5ccd32a Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 11 May 2021 20:13:57 +0800
Subject: [PATCH] xid-related-and-testcase
---
src/backend/access/transam/xact.c | 26 +
src/backend/executor/execMain.c | 3 +
src/backend/optimizer/plan/planner.c | 18 +-
src/include/access/xact.h | 1 +
src/test/regress/expected/insert_parallel.out | 580 ++++++++++++++++++
src/test/regress/parallel_schedule | 1 +
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 346 +++++++++++
8 files changed, 967 insertions(+), 9 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 441445927e..2d68e4633a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1014,6 +1014,32 @@ IsInParallelMode(void)
return CurrentTransactionState->parallelModeLevel != 0;
}
+/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
/*
* CommandCounterIncrement
*/
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index df3d7f9a8b..b7bd95f852 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index dbc2827d20..7736813230 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index c04e6a98d7..34cfaf542c 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,6 +466,7 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
/*
* IsModifySupportedInParallelMode
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..ca486c63e6
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,580 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+--
+-- END: setup some tables and data needed by the tests.
+--
+-- 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
+);
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_max_parallel_hazard('para_insert_f1');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names2');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_max_parallel_hazard('names4');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_max_parallel_hazard('temp_names');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('table_check_b');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into table_check_b select * from names;
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('dom_table_u');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a091300857..c6741a98aa 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -95,6 +95,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 5644847601..638b7a23d0 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -151,6 +151,7 @@ test: stats_ext
test: collate.linux.utf8
test: select_parallel
test: write_parallel
+test: insert_parallel
test: publication
test: subscription
test: select_views
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..0686cb6646
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,346 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+-- 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
+);
+
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+select pg_get_max_parallel_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+select pg_get_max_parallel_hazard('names2');
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+select pg_get_max_parallel_hazard('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+select pg_get_max_parallel_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+select pg_get_max_parallel_hazard('table_check_b');
+-- insert into table_check_b select * from names;
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+select pg_get_max_parallel_hazard('dom_table_u');
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
v3-POC-0001-get-parallel-safety-functions.patchapplication/octet-stream; name=v3-POC-0001-get-parallel-safety-functions.patchDownload
From 21f7aea2b3b8f265957666d12dc1106969d84a07 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 11 May 2021 19:26:59 +0800
Subject: [PATCH] get-parallel-safety-functions
provide a utility function pg_get_parallel_safety('table_name') that returns records of
(objid, classid, parallel_safety) that represent the parallel safety of objects that
determine the parallel safety of the specified table. The user can use this function
to identify problematic objects when a parallel DML fails or is not parallelized in an
expected manner.
When detecting an parallel unsafe/restricted function in index(or others can have an expression),
return both the function oid and the index oid.
provide a utility function pg_get_max_parallel_hazard('table_name') that returns the worst function hazard
property that can found in the given relation. Users can use this function to do a quick check
without caring about the specified parallel related objects.
---
src/backend/optimizer/plan/planner.c | 3 +-
src/backend/optimizer/util/clauses.c | 626 ++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 101 +++++
src/backend/utils/cache/typcache.c | 14 +
src/include/access/xact.h | 14 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 11 +
src/include/utils/typcache.h | 2 +
8 files changed, 787 insertions(+), 6 deletions(-)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..dbc2827d20 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d9ad4efc5e..7c21f6e60e 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,13 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -43,6 +50,9 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +61,8 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all;
+ List *func_oids;
+ PartitionDirectory partition_directory;
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -98,6 +113,20 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context);
+static bool target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_max_parallel_hazard(Relation index_rel, List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting, max_parallel_hazard_context *context);
+static bool target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -149,6 +178,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
* Aggregate-function clause manipulation
@@ -620,6 +650,10 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
+
(void) max_parallel_hazard_walker((Node *) parse, &context);
return context.max_hazard;
}
@@ -651,6 +685,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -682,7 +719,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -699,6 +736,63 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+static bool
+parallel_safety_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ cont->func_oids = lappend(cont->func_oids,
+ make_safety_object(func_id, ProcedureRelationId, proparallel));
+ }
+
+ return false;
+}
+
+/* Check parallel unsafe/restricted function in expression */
+static bool
+parallel_safety_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_safety_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_max_parallel_hazard(domain->resulttype, context) &&
+ !context->check_all)
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_safety_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -854,6 +948,536 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+List*
+target_rel_max_parallel_hazard(RangeVar *relrv, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.func_oids = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_openrv(relrv, AccessShareLock);
+
+ (void) target_rel_all_parallel_hazard_recurse(targetRel, &context);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.func_oids;
+}
+
+
+static bool
+target_rel_all_parallel_hazard_recurse(Relation rel, max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(rel->rd_rel->oid, RelationRelationId,
+ PROPARALLEL_RESTRICTED));
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef)
+ {
+ Node *defaultexpr;
+ defaultexpr = build_column_default(rel, attnum);
+ if (parallel_safety_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_max_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_max_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified relation's
+ * trigger data.
+ */
+static bool
+target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgfoid, ProcedureRelationId, proparallel));
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgoid, TriggerRelationId, proparallel));
+ }
+ }
+
+ return false;
+}
+
+static bool
+index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ Form_pg_index indexStruct;
+ int i;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ if (ii_Expressions != NIL)
+ {
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_safety_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+ }
+
+ if (ii_Predicate != NIL)
+ {
+ if (parallel_safety_walker((Node *) ii_Predicate, context))
+ return true;
+ }
+
+ /*
+ * Check parallel-safety of any index AM support functions.
+ */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+ if (nsupport > 0)
+ {
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+
+ Oid funcOid = index_rel->rd_support[i];
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_index_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any existing index
+ * expressions or index predicate of a specified relation.
+ */
+static bool
+target_rel_index_max_parallel_hazard(Relation rel, max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_max_parallel_hazard(index_rel, ii_Expressions,
+ ii_Predicate, context->check_all,
+ context->max_interesting,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(index_oid, IndexRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified DOMAIN type.
+ * Only any CHECK expressions are examined for parallel-safety.
+ */
+static bool
+target_rel_domain_max_parallel_hazard(Oid typid, max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_safety_walker((Node *) r->check_expr, context) &&
+ !context->check_all)
+ return true;
+
+ /* Add the Constraint itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_domain_constraint_oid(typid, r->name, false),
+ ConstraintRelationId,
+ context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any partitions of a
+ * of a specified relation.
+ */
+static bool
+target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs, *qual;
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_safety_walker((Node *) qual, context))
+ return true;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_safety_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_all_parallel_hazard_recurse(part_rel, context);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ break;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any CHECK expressions or
+ * CHECK constraints related to the specified relation.
+ */
+static List*
+target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ List *temp_objects;
+
+ tupdesc = RelationGetDescr(rel);
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ if (tupdesc->constr != NULL && tupdesc->constr->num_check > 0)
+ {
+ int i;
+
+ ConstrCheck *check = tupdesc->constr->check;
+
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) check_expr, context);
+
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_relation_constraint_oid(rel->rd_rel->oid, check->ccname, true), ConstraintRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+ }
+
+ return false;
+}
+
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4dfd7..aa49b6bbc3 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,103 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_max_parallel_hazard(PG_FUNCTION_ARGS)
+{
+ RangeVar *relvar;
+ text *relname_text;
+ char max_parallel_hazard;
+
+ relname_text = PG_GETARG_TEXT_PP(0);
+ relvar = makeRangeVarFromNameList(textToQualifiedNameList(relname_text));
+ (void) target_rel_max_parallel_hazard(relvar, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ RangeVar *relvar;
+ text *relname_text;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+
+ relname_text = PG_GETARG_TEXT_PP(0);
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+
+ MemoryContextSwitchTo(oldcontext);
+
+ relvar = makeRangeVarFromNameList(textToQualifiedNameList(relname_text));
+ objects = target_rel_max_parallel_hazard(relvar, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 4915ef5934..260f5d45c8 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,20 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+
+List *GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index f49a57b35e..c04e6a98d7 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -467,4 +467,18 @@ extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
+
#endif /* XACT_H */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 26c3fc0f6b..814c782b08 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3765,6 +3765,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_max_parallel_hazard', prorettype => 'char', proargtypes => 'text',
+ prosrc => 'pg_get_max_parallel_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3772,11 +3786,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887a85..aaa6e07b3e 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -52,5 +59,9 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_max_parallel_hazard(RangeVar *relrv, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a4b7..28ca7d8a6e 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.18.4
v3-POC-0002-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v3-POC-0002-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From b4732b441f012522fa0482a08ab655cd9fe18f31 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 11 May 2021 19:42:08 +0800
Subject: [PATCH] CREATE-ALTER-TABLE-PARALLEL-DML
Enabling users to declare that the table allows parallel data modification,
Add a table property that represents parallel safety of the table for DML
statement execution. Users specify it as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u', 'r', or 's',
just like pg_proc's proparallel. The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have the specified parallel safety or safer one.
The user is responsible for its correctness. If the parallel processes
find an object that is less safer than the assumed parallel safety during
statement execution, it throws an ERROR and abort the statement execution.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 +++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 25 +++++-
src/backend/parser/gram.y | 64 ++++++++++----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 ++++++++--
src/bin/pg_dump/pg_dump.h | 1 +
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 238 insertions(+), 33 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..88fcd57082 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 431e62e389..41aae0c74b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -301,6 +301,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -403,7 +404,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -960,6 +962,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1153,6 +1156,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1300,6 +1304,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8ded2b53d4..fc46c7a40b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -933,6 +934,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 933a0734d1..e4a05205be 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..2151121066 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce882012e..45aacc8b7a 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9ccb..6f25c231e9 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 591bf01189..6f13908443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -933,6 +936,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support parallel data modification on foreign or temporary table")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -951,6 +976,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4192,6 +4218,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4725,6 +4752,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5127,6 +5159,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18645,3 +18680,55 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal((Value *) def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support parallel data modification on foreign or temporary table")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 036fa69d17..ec3834fae9 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..2d77a88726 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a89b0..6bf8787091 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3531,6 +3531,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d093dd..aaed9d1614 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1284,6 +1285,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14f0e..16c66a8ada 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2703,6 +2704,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea07df..09f5f0d59d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7c21f6e60e..c8fdeb324b 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -177,7 +177,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
-
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -645,6 +645,7 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
@@ -654,7 +655,27 @@ max_parallel_hazard(Query *parse)
context.func_oids = NIL;
context.partition_directory = NULL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index aaf1a51f68..90d9e2abd5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -610,6 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> hash_partbound_elem
%type <str> optColumnCompression
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +655,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2684,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3277,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3291,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3311,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3332,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3353,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3375,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3397,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4079,6 +4096,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4226,7 +4247,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4235,6 +4256,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5014,7 +5036,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5026,15 +5048,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5046,15 +5069,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5067,15 +5091,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5088,10 +5113,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15561,6 +15587,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16101,6 +16128,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index bd88f6105b..3ecb1a005e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1870,6 +1870,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3356,7 +3357,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3506,6 +3508,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e384690d94..10217575ae 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6282,6 +6282,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relproparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6386,7 +6387,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6478,7 +6479,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6531,7 +6532,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6584,7 +6585,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6637,7 +6638,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6688,7 +6689,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6736,7 +6737,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6784,7 +6785,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6831,7 +6832,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6900,6 +6901,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relproparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6955,6 +6957,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relproparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16480,6 +16483,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a09c..e083593d67 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..b59975919b 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729436..af280b5095 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fea3123251..9bea644bf3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2168,6 +2169,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d847..6b532b034a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..05222faccd 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855ac6..5ea225ac2d 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5438..e1f5678eef 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.18.4
On Tue, May 11, 2021 at 6:11 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
So, users need to check count(*) for this to determine
parallel-safety? How about if we provide a wrapper function on top
of this function or a separate function that returns char to
indicate whether it is safe, unsafe, or restricted to perform a DML
operation on the table?Such wrapper function make sense.
Thanks for the suggestion, and I agree.
I will add another wrapper function and post new version patches soon.Attaching new version patches with the following changes:
0001
Add a new function pg_get_max_parallel_hazard('table_name') returns char('s', 'u', 'r')
which indicate whether it is safe, unsafe, or restricted to perform a DML.
Thanks for the patches. I think we should have the table name as
regclass type for pg_get_max_parallel_hazard? See, pg_relation_size,
pg_table_size, pg_filenode_relation and so on.
With Regards,
Bharath Rupireddy.
EnterpriseDB: http://www.enterprisedb.com
On Tue, May 11, 2021 at 10:41 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Attaching new version patches with the following changes:
0001
Add a new function pg_get_max_parallel_hazard('table_name') returns char('s', 'u', 'r')
which indicate whether it is safe, unsafe, or restricted to perform a DML.
Currently the 1st patch actually also contains the changes for the
Parallel-SELECT-for-INSERT functionality. This is not obvious.
So I think this should be split out into a separate patch (i.e. the
minor planner update and related planner comment changes,
is_parallel_allowed_for_modify() function, max_parallel_hazard()
update, XID changes).
Also, the regression tests' "serial_schedule" file has been removed
since you posted the v3-POC patches, so you need to remove updates for
that from your 3rd patch.
How about reorganisation of the patches like the following?
0001: CREATE ALTER TABLE PARALLEL DML
0002: parallel-SELECT-for-INSERT (planner changes,
max_parallel_hazard() update, XID changes)
0003: pg_get_parallel_safety()
0004: regression test updates
Regards,
Greg Nancarrow
Fujitsu Australia
Currently the 1st patch actually also contains the changes for the
Parallel-SELECT-for-INSERT functionality. This is not obvious.
So I think this should be split out into a separate patch (i.e. the minor planner
update and related planner comment changes,
is_parallel_allowed_for_modify() function, max_parallel_hazard() update, XID
changes).
Also, the regression tests' "serial_schedule" file has been removed since you
posted the v3-POC patches, so you need to remove updates for that from your
3rd patch.
Thanks for the comments, I have posted new version patches with this change.
How about reorganisation of the patches like the following?
0001: CREATE ALTER TABLE PARALLEL DML
0002: parallel-SELECT-for-INSERT (planner changes,
max_parallel_hazard() update, XID changes)
0003: pg_get_parallel_safety()
0004: regression test updates
Thanks, it looks good and I reorganized the latest patchset in this way.
Attaching new version patches with the following change.
0003
Change functions arg type to regclass.
0004
remove updates for "serial_schedule".
Best regards,
houzj
Attachments:
v4-POC-0004-regression-test-updates.patchapplication/octet-stream; name=v4-POC-0004-regression-test-updates.patchDownload
From 3fdebecece68969cc76d37bee6214ab6341a4b47 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 14 May 2021 16:20:58 +0800
Subject: [PATCH 4/4] regression-test-updates
---
src/test/regress/expected/insert_parallel.out | 580 ++++++++++++++++++
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 346 +++++++++++
3 files changed, 927 insertions(+)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..ca486c63e6
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,580 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+--
+-- END: setup some tables and data needed by the tests.
+--
+-- 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
+);
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_max_parallel_hazard('para_insert_f1');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names2');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_max_parallel_hazard('names4');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_max_parallel_hazard('temp_names');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('table_check_b');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into table_check_b select * from names;
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('dom_table_u');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..0686cb6646
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,346 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+-- 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
+);
+
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+select pg_get_max_parallel_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+select pg_get_max_parallel_hazard('names2');
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+select pg_get_max_parallel_hazard('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+select pg_get_max_parallel_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+select pg_get_max_parallel_hazard('table_check_b');
+-- insert into table_check_b select * from names;
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+select pg_get_max_parallel_hazard('dom_table_u');
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
v4-POC-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v4-POC-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 1a976f31d16b19c144b5e1d6f4c946c276bf2b84 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 14 May 2021 15:21:42 +0800
Subject: [PATCH 1/4] CREATE-ALTER-TABLE-PARALLEL-DML
Enabling users to declare that the table allows parallel data modification,
Add a table property that represents parallel safety of the table for DML
statement execution. Users specify it as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u', 'r', or 's',
just like pg_proc's proparallel. The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have the specified parallel safety or safer one.
The user is responsible for its correctness. If the parallel processes
find an object that is less safer than the assumed parallel safety during
statement execution, it throws an ERROR and abort the statement execution.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 +++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 64 ++++++++++----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 ++++++++--
src/bin/pg_dump/pg_dump.h | 1 +
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../test_ddl_deparse/test_ddl_deparse.c | 3 +
25 files changed, 215 insertions(+), 31 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..88fcd57082 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 431e62e389..41aae0c74b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -301,6 +301,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -403,7 +404,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -960,6 +962,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1153,6 +1156,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1300,6 +1304,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0f8cfae4ec..dfaebd4463 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -933,6 +934,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index bf81f6ccc5..d0a9fea686 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..2151121066 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce882012e..45aacc8b7a 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9ccb..6f25c231e9 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ebc62034d2..54b738a760 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -933,6 +936,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support parallel data modification on foreign or temporary table")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -951,6 +976,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4192,6 +4218,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4725,6 +4752,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5127,6 +5159,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18645,3 +18680,55 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal((Value *) def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support parallel data modification on foreign or temporary table")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c6af..8baebe09a2 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..2d77a88726 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a89b0..6bf8787091 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3531,6 +3531,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d093dd..aaed9d1614 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1284,6 +1285,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14f0e..16c66a8ada 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2703,6 +2704,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea07df..09f5f0d59d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index aaf1a51f68..90d9e2abd5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -610,6 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> hash_partbound_elem
%type <str> optColumnCompression
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +655,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2684,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3277,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3291,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3311,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3332,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3353,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3375,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3397,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4079,6 +4096,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4226,7 +4247,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4235,6 +4256,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5014,7 +5036,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5026,15 +5048,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5046,15 +5069,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5067,15 +5091,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5088,10 +5113,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15561,6 +15587,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16101,6 +16128,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615e76..ef6200d85b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1870,6 +1870,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3356,7 +3357,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3506,6 +3508,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e384690d94..2aec594c64 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6282,6 +6282,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relproparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6386,7 +6387,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6478,7 +6479,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6531,7 +6532,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6584,7 +6585,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6637,7 +6638,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6688,7 +6689,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6736,7 +6737,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6784,7 +6785,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6831,7 +6832,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6900,6 +6901,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relproparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6955,6 +6957,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relproparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16480,6 +16483,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a09c..e083593d67 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..b59975919b 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729436..af280b5095 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342019..dcdf6db4f1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2168,6 +2169,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d847..6b532b034a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..05222faccd 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855ac6..5ea225ac2d 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5438..e1f5678eef 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.18.4
v4-POC-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v4-POC-0002-parallel-SELECT-for-INSERT.patchDownload
From 91a0fdb7e67dae319a135ec2115ab563ed29be45 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 14 May 2021 15:33:52 +0800
Subject: [PATCH 2/4] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++
src/backend/executor/execMain.c | 3 +
src/backend/optimizer/plan/planner.c | 21 +++----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 441445927e..2d68e4633a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1014,6 +1014,32 @@ IsInParallelMode(void)
return CurrentTransactionState->parallelModeLevel != 0;
}
+/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
/*
* CommandCounterIncrement
*/
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..ea685f0846 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..7736813230 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e117ab976e..1892872e2c 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f6862da..fd3f86bf7c 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887a85..32b56565e5 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.18.4
v4-POC-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v4-POC-0003-get-parallel-safety-functions.patchDownload
From 1f3e073b83c3845dca162da64813df9ffe7689a6 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 14 May 2021 15:43:33 +0800
Subject: [PATCH 3/4] get-parallel-safety-functions
provide a utility function pg_get_parallel_safety(regclass) that returns records of
(objid, classid, parallel_safety) that represent the parallel safety of objects that
determine the parallel safety of the specified table. The user can use this function
to identify problematic objects when a parallel DML fails or is not parallelized in an
expected manner.
When detecting an parallel unsafe/restricted function in index(or others can have an expression),
return both the function oid and the index oid.
provide a utility function pg_get_max_parallel_hazard(regclass) that returns the worst function hazard
property that can found in the given relation. Users can use this function to do a quick check
without caring about the specified parallel related objects.
---
src/backend/optimizer/util/clauses.c | 563 ++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 +++++
src/backend/utils/cache/typcache.c | 14 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 700 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 1892872e2c..0e949b6bb7 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all;
+ List *func_oids;
+ PartitionDirectory partition_directory;
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,20 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context);
+static bool target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_max_parallel_hazard(Relation index_rel, List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting, max_parallel_hazard_context *context);
+static bool target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +178,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +652,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +707,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +741,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +758,63 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+static bool
+parallel_safety_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ cont->func_oids = lappend(cont->func_oids,
+ make_safety_object(func_id, ProcedureRelationId, proparallel));
+ }
+
+ return false;
+}
+
+/* Check parallel unsafe/restricted function in expression */
+static bool
+parallel_safety_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_safety_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_max_parallel_hazard(domain->resulttype, context) &&
+ !context->check_all)
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_safety_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -881,6 +970,478 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+List*
+target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.func_oids = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_all_parallel_hazard_recurse(targetRel, &context);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.func_oids;
+}
+
+
+static bool
+target_rel_all_parallel_hazard_recurse(Relation rel, max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(rel->rd_rel->oid, RelationRelationId,
+ PROPARALLEL_RESTRICTED));
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef)
+ {
+ Node *defaultexpr;
+ defaultexpr = build_column_default(rel, attnum);
+ if (parallel_safety_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_max_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_max_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified relation's
+ * trigger data.
+ */
+static bool
+target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgfoid, ProcedureRelationId, proparallel));
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgoid, TriggerRelationId, proparallel));
+ }
+ }
+
+ return false;
+}
+
+static bool
+index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ Form_pg_index indexStruct;
+ int i;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ if (ii_Expressions != NIL)
+ {
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_safety_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+ }
+
+ if (ii_Predicate != NIL)
+ {
+ if (parallel_safety_walker((Node *) ii_Predicate, context))
+ return true;
+ }
+
+ /*
+ * Check parallel-safety of any index AM support functions.
+ */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+ if (nsupport > 0)
+ {
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+
+ Oid funcOid = index_rel->rd_support[i];
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_index_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any existing index
+ * expressions or index predicate of a specified relation.
+ */
+static bool
+target_rel_index_max_parallel_hazard(Relation rel, max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_max_parallel_hazard(index_rel, ii_Expressions,
+ ii_Predicate, context->check_all,
+ context->max_interesting,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(index_oid, IndexRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified DOMAIN type.
+ * Only any CHECK expressions are examined for parallel-safety.
+ */
+static bool
+target_rel_domain_max_parallel_hazard(Oid typid, max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_safety_walker((Node *) r->check_expr, context) &&
+ !context->check_all)
+ return true;
+
+ /* Add the Constraint itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_domain_constraint_oid(typid, r->name, false),
+ ConstraintRelationId,
+ context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any partitions of a
+ * of a specified relation.
+ */
+static bool
+target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs, *qual;
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_safety_walker((Node *) qual, context))
+ return true;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_safety_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_all_parallel_hazard_recurse(part_rel, context);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ break;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any CHECK expressions or
+ * CHECK constraints related to the specified relation.
+ */
+static List*
+target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ List *temp_objects;
+
+ tupdesc = RelationGetDescr(rel);
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ if (tupdesc->constr != NULL && tupdesc->constr->num_check > 0)
+ {
+ int i;
+
+ ConstrCheck *check = tupdesc->constr->check;
+
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) check_expr, context);
+
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_relation_constraint_oid(rel->rd_rel->oid, check->ccname, true), ConstraintRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+ }
+
+ return false;
+}
+
/*
* is_parallel_allowed_for_modify
*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4dfd7..18eabcd630 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_max_parallel_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_max_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_max_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 35c8cf7b24..0573541524 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,20 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+
+List *GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4607..2f8c52814a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_max_parallel_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_max_parallel_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b56565e5..7ceb750b50 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a4b7..28ca7d8a6e 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.18.4
So, users need to check count(*) for this to determine
parallel-safety? How about if we provide a wrapper function on
top of this function or a separate function that returns char to
indicate whether it is safe, unsafe, or restricted to perform a
DML operation on the table?Such wrapper function make sense.
Thanks for the suggestion, and I agree.
I will add another wrapper function and post new version patches soon.Attaching new version patches with the following changes:
0001
Add a new function pg_get_max_parallel_hazard('table_name') returns
char('s', 'u', 'r') which indicate whether it is safe, unsafe, or restricted toperform a DML.
Thanks for the patches. I think we should have the table name as regclass type
for pg_get_max_parallel_hazard? See, pg_relation_size, pg_table_size,
pg_filenode_relation and so on.
Thanks for the comment.
I have changed the type to regclass in the latest patchset.
Best regards,
houzj
On Fri, May 14, 2021 at 6:24 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Thanks for the comments, I have posted new version patches with this change.
How about reorganisation of the patches like the following?
0001: CREATE ALTER TABLE PARALLEL DML
0002: parallel-SELECT-for-INSERT (planner changes,
max_parallel_hazard() update, XID changes)
0003: pg_get_parallel_safety()
0004: regression test updatesThanks, it looks good and I reorganized the latest patchset in this way.
Attaching new version patches with the following change.
0003
Change functions arg type to regclass.0004
remove updates for "serial_schedule".
I've got some comments for the V4 set of patches:
(0001)
(i) Patch comment needs a little updating (suggested change is below):
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have,
at worst, the specified parallel safety. The user is responsible for
its correctness.
---
NOTE: The following sentence was removed from the original V4 0001
patch comment (since this version of the patch is not doing runtime
parallel-safety checks on functions):.
If the parallel processes
find an object that is less safer than the assumed parallel safety during
statement execution, it throws an ERROR and abort the statement execution.
(ii) Update message to say "a foreign ...":
BEFORE:
+ errmsg("cannot support parallel data modification on foreign or
temporary table")));
AFTER:
+ errmsg("cannot support parallel data modification on a foreign or
temporary table")));
(iii) strVal() macro already casts to "Value *", so the cast can be
removed from the following:
+ char *parallel = strVal((Value *) def);
(0003)
(i) Suggested updates to the patch comment:
Provide a utility function "pg_get_parallel_safety(regclass)" that
returns records of
(objid, classid, parallel_safety) for all parallel unsafe/restricted
table-related objects
from which the table's parallel DML safety is determined. The user can
use this information
during development in order to accurately declare a table's parallel
DML safety. or to
identify any problematic objects if a parallel DML fails or behaves
unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the
function oid and the index oid are returned.
Provide a utility function "pg_get_max_parallel_hazard(regclass)" that
returns the worst
parallel DML safety hazard that can be found in the given relation.
Users can use this
function to do a quick check without caring about specific
parallel-related objects.
Regards,
Greg Nancarrow
Fujitsu Australia
From: Greg Nancarrow <gregn4422@gmail.com>
Sent: Wednesday, May 19, 2021 7:55 PM
On Fri, May 14, 2021 at 6:24 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Thanks for the comments, I have posted new version patches with this
change.
How about reorganisation of the patches like the following?
0001: CREATE ALTER TABLE PARALLEL DML
0002: parallel-SELECT-for-INSERT (planner changes,
max_parallel_hazard() update, XID changes)
0003: pg_get_parallel_safety()
0004: regression test updatesThanks, it looks good and I reorganized the latest patchset in this way.
Attaching new version patches with the following change.
0003
Change functions arg type to regclass.0004
remove updates for "serial_schedule".I've got some comments for the V4 set of patches:
(0001)
(i) Patch comment needs a little updating (suggested change is below):
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have,
at worst, the specified parallel safety. The user is responsible for
its correctness.---
NOTE: The following sentence was removed from the original V4 0001
patch comment (since this version of the patch is not doing runtime
parallel-safety checks on functions):.If the parallel processes
find an object that is less safer than the assumed parallel safety during
statement execution, it throws an ERROR and abort the statement execution.(ii) Update message to say "a foreign ...":
BEFORE:
+ errmsg("cannot support parallel data modification on foreign or
temporary table")));AFTER:
+ errmsg("cannot support parallel data modification on a foreign or
temporary table")));(iii) strVal() macro already casts to "Value *", so the cast can be
removed from the following:+ char *parallel = strVal((Value *) def);
(0003)
(i) Suggested updates to the patch comment:
Provide a utility function "pg_get_parallel_safety(regclass)" that
returns records of
(objid, classid, parallel_safety) for all parallel unsafe/restricted
table-related objects
from which the table's parallel DML safety is determined. The user can
use this information
during development in order to accurately declare a table's parallel
DML safety. or to
identify any problematic objects if a parallel DML fails or behaves
unexpectedly.When the use of an index-related parallel unsafe/restricted function
is detected, both the
function oid and the index oid are returned.Provide a utility function "pg_get_max_parallel_hazard(regclass)" that
returns the worst
parallel DML safety hazard that can be found in the given relation.
Users can use this
function to do a quick check without caring about specific
parallel-related objects.
Thanks for the comments and your descriptions looks good.
Attaching v5 patchset with all these changes.
Best regards,
houzj
Attachments:
v5-POC-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v5-POC-0002-parallel-SELECT-for-INSERT.patchDownload
From 91a0fdb7e67dae319a135ec2115ab563ed29be45 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 14 May 2021 15:33:52 +0800
Subject: [PATCH 2/4] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++
src/backend/executor/execMain.c | 3 +
src/backend/optimizer/plan/planner.c | 21 +++----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 441445927e..2d68e4633a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1014,6 +1014,32 @@ IsInParallelMode(void)
return CurrentTransactionState->parallelModeLevel != 0;
}
+/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
/*
* CommandCounterIncrement
*/
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..ea685f0846 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..7736813230 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e117ab976e..1892872e2c 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f6862da..fd3f86bf7c 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887a85..32b56565e5 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.18.4
v5-POC-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v5-POC-0003-get-parallel-safety-functions.patchDownload
From 1f3e073b83c3845dca162da64813df9ffe7689a6 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 14 May 2021 15:43:33 +0800
Subject: [PATCH 3/4] get-parallel-safety-functions
Provide a utility function "pg_get_parallel_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_max_parallel_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 563 ++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 +++++
src/backend/utils/cache/typcache.c | 14 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 700 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 1892872e2c..0e949b6bb7 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all;
+ List *func_oids;
+ PartitionDirectory partition_directory;
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,20 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context);
+static bool target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_max_parallel_hazard(Relation index_rel, List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting, max_parallel_hazard_context *context);
+static bool target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +178,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +652,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +707,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +741,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +758,63 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+static bool
+parallel_safety_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ cont->func_oids = lappend(cont->func_oids,
+ make_safety_object(func_id, ProcedureRelationId, proparallel));
+ }
+
+ return false;
+}
+
+/* Check parallel unsafe/restricted function in expression */
+static bool
+parallel_safety_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_safety_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_max_parallel_hazard(domain->resulttype, context) &&
+ !context->check_all)
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_safety_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -881,6 +970,478 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+List*
+target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.func_oids = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_all_parallel_hazard_recurse(targetRel, &context);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.func_oids;
+}
+
+
+static bool
+target_rel_all_parallel_hazard_recurse(Relation rel, max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(rel->rd_rel->oid, RelationRelationId,
+ PROPARALLEL_RESTRICTED));
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef)
+ {
+ Node *defaultexpr;
+ defaultexpr = build_column_default(rel, attnum);
+ if (parallel_safety_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_max_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_max_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified relation's
+ * trigger data.
+ */
+static bool
+target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgfoid, ProcedureRelationId, proparallel));
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgoid, TriggerRelationId, proparallel));
+ }
+ }
+
+ return false;
+}
+
+static bool
+index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ Form_pg_index indexStruct;
+ int i;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ if (ii_Expressions != NIL)
+ {
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_safety_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+ }
+
+ if (ii_Predicate != NIL)
+ {
+ if (parallel_safety_walker((Node *) ii_Predicate, context))
+ return true;
+ }
+
+ /*
+ * Check parallel-safety of any index AM support functions.
+ */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+ if (nsupport > 0)
+ {
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+
+ Oid funcOid = index_rel->rd_support[i];
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_index_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any existing index
+ * expressions or index predicate of a specified relation.
+ */
+static bool
+target_rel_index_max_parallel_hazard(Relation rel, max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_max_parallel_hazard(index_rel, ii_Expressions,
+ ii_Predicate, context->check_all,
+ context->max_interesting,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(index_oid, IndexRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified DOMAIN type.
+ * Only any CHECK expressions are examined for parallel-safety.
+ */
+static bool
+target_rel_domain_max_parallel_hazard(Oid typid, max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_safety_walker((Node *) r->check_expr, context) &&
+ !context->check_all)
+ return true;
+
+ /* Add the Constraint itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_domain_constraint_oid(typid, r->name, false),
+ ConstraintRelationId,
+ context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any partitions of a
+ * of a specified relation.
+ */
+static bool
+target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs, *qual;
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_safety_walker((Node *) qual, context))
+ return true;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_safety_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_all_parallel_hazard_recurse(part_rel, context);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ break;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any CHECK expressions or
+ * CHECK constraints related to the specified relation.
+ */
+static List*
+target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ List *temp_objects;
+
+ tupdesc = RelationGetDescr(rel);
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ if (tupdesc->constr != NULL && tupdesc->constr->num_check > 0)
+ {
+ int i;
+
+ ConstrCheck *check = tupdesc->constr->check;
+
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) check_expr, context);
+
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_relation_constraint_oid(rel->rd_rel->oid, check->ccname, true), ConstraintRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+ }
+
+ return false;
+}
+
/*
* is_parallel_allowed_for_modify
*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4dfd7..18eabcd630 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_max_parallel_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_max_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_max_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 35c8cf7b24..0573541524 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,20 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+
+List *GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4607..2f8c52814a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_max_parallel_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_max_parallel_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b56565e5..7ceb750b50 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a4b7..28ca7d8a6e 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.18.4
v5-POC-0004-regression-test-updates.patchapplication/octet-stream; name=v5-POC-0004-regression-test-updates.patchDownload
From 3fdebecece68969cc76d37bee6214ab6341a4b47 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 14 May 2021 16:20:58 +0800
Subject: [PATCH 4/4] regression-test-updates
---
src/test/regress/expected/insert_parallel.out | 580 ++++++++++++++++++
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 346 +++++++++++
3 files changed, 927 insertions(+)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..ca486c63e6
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,580 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+--
+-- END: setup some tables and data needed by the tests.
+--
+-- 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
+);
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_max_parallel_hazard('para_insert_f1');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names2');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_max_parallel_hazard('names4');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_max_parallel_hazard('temp_names');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('table_check_b');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into table_check_b select * from names;
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('dom_table_u');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..0686cb6646
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,346 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+-- 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
+);
+
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+select pg_get_max_parallel_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+select pg_get_max_parallel_hazard('names2');
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+select pg_get_max_parallel_hazard('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+select pg_get_max_parallel_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+select pg_get_max_parallel_hazard('table_check_b');
+-- insert into table_check_b select * from names;
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+select pg_get_max_parallel_hazard('dom_table_u');
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
v5-POC-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v5-POC-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 1a976f31d16b19c144b5e1d6f4c946c276bf2b84 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Fri, 14 May 2021 15:21:42 +0800
Subject: [PATCH 1/4] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 +++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 64 ++++++++++----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 ++++++++--
src/bin/pg_dump/pg_dump.h | 1 +
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../test_ddl_deparse/test_ddl_deparse.c | 3 +
25 files changed, 215 insertions(+), 31 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..88fcd57082 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 431e62e389..41aae0c74b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -301,6 +301,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -403,7 +404,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -960,6 +962,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1153,6 +1156,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1300,6 +1304,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0f8cfae4ec..dfaebd4463 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -933,6 +934,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index bf81f6ccc5..d0a9fea686 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..2151121066 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce882012e..45aacc8b7a 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9ccb..6f25c231e9 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ebc62034d2..54b738a760 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -933,6 +936,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support parallel data modification on a foreign or temporary table")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -951,6 +976,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4192,6 +4218,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4725,6 +4752,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5127,6 +5159,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18645,3 +18680,55 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support parallel data modification on a foreign or temporary table")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c6af..8baebe09a2 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..2d77a88726 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a89b0..6bf8787091 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3531,6 +3531,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d093dd..aaed9d1614 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1284,6 +1285,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14f0e..16c66a8ada 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2703,6 +2704,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea07df..09f5f0d59d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index aaf1a51f68..90d9e2abd5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -610,6 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> hash_partbound_elem
%type <str> optColumnCompression
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +655,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2684,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3277,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3291,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3311,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3332,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3353,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3375,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3397,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4079,6 +4096,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4226,7 +4247,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4235,6 +4256,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5014,7 +5036,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5026,15 +5048,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5046,15 +5069,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5067,15 +5091,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5088,10 +5113,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15561,6 +15587,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16101,6 +16128,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615e76..ef6200d85b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1870,6 +1870,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3356,7 +3357,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3506,6 +3508,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e384690d94..2aec594c64 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6282,6 +6282,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relproparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6386,7 +6387,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6478,7 +6479,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6531,7 +6532,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6584,7 +6585,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6637,7 +6638,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6688,7 +6689,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6736,7 +6737,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6784,7 +6785,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6831,7 +6832,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6900,6 +6901,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relproparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6955,6 +6957,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relproparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16480,6 +16483,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a09c..e083593d67 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..b59975919b 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729436..af280b5095 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342019..dcdf6db4f1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2168,6 +2169,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d847..6b532b034a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..05222faccd 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855ac6..5ea225ac2d 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5438..e1f5678eef 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.18.4
On Mon, May 24, 2021 at 3:15 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Thanks for the comments and your descriptions looks good.
Attaching v5 patchset with all these changes.
A few other minor things I noticed:
(1) error message wording when declaring a table SAFE for parallel DML
src/backend/commands/tablecmds.c
Since data modification for the RELKIND_FOREIGN_TABLE and
RELPERSISTENCE_TEMP types are allowed in the parallel-restricted case
(i.e. leader may modify in parallel mode)
I'm thinking it may be better to use wording like:
"cannot support foreign or temporary table data modification by
parallel workers"
instead of
"cannot support parallel data modification on a foreign or temporary table"
There are TWO places where this error message is used.
(What do you think?)
(2) Minor formatting issue
src/backend/optimizer/util/clauses.c
static safety_object *make_safety_object(Oid objid, Oid classid,
char proparallel)
should be:
static safety_object *
make_safety_object(Oid objid, Oid classid, char proparallel)
(3) Minor formatting issue
src/backend/utils/cache/typcache.c
List *GetDomainConstraints(Oid type_id)
should be:
List *
GetDomainConstraints(Oid type_id)
Regards,
Greg Nancarrow
Fujitsu Australia
From: Greg Nancarrow <gregn4422@gmail.com>
Sent: Friday, May 28, 2021 4:42 PM
On Mon, May 24, 2021 at 3:15 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Thanks for the comments and your descriptions looks good.
Attaching v5 patchset with all these changes.A few other minor things I noticed:
(1) error message wording when declaring a table SAFE for parallel DML
src/backend/commands/tablecmds.c
Since data modification for the RELKIND_FOREIGN_TABLE and
RELPERSISTENCE_TEMP types are allowed in the parallel-restricted case (i.e.
leader may modify in parallel mode) I'm thinking it may be better to use
wording like:"cannot support foreign or temporary table data modification by parallel
workers"instead of
"cannot support parallel data modification on a foreign or temporary table"
There are TWO places where this error message is used.
(What do you think?)
I think your change looks good.
I used your msg in the latest patchset.
(2) Minor formatting issue
src/backend/optimizer/util/clauses.c
static safety_object *make_safety_object(Oid objid, Oid classid, char
proparallel)should be:
static safety_object *
make_safety_object(Oid objid, Oid classid, char proparallel)
Changed.
(3) Minor formatting issue
src/backend/utils/cache/typcache.c
List *GetDomainConstraints(Oid type_id)
should be:
List *
GetDomainConstraints(Oid type_id)
Changed.
Attaching v6 patchset.
And I registered it in CF https://commitfest.postgresql.org/33/3143/,
comments are welcome.
Best regards,
houzj
Attachments:
v6-0004-regression-test-and-doc-updates.patchapplication/octet-stream; name=v6-0004-regression-test-and-doc-updates.patchDownload
From 847432ca595a2e9b91b42cd7be3aa892b496a733 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 12:51:00 +0800
Subject: [PATCH] regression-test-and doc updates
---
doc/src/sgml/func.sgml | 41 ++
doc/src/sgml/ref/create_foreign_table.sgml | 24 ++
doc/src/sgml/ref/create_table.sgml | 25 ++
doc/src/sgml/ref/create_table_as.sgml | 23 +
src/test/regress/expected/insert_parallel.out | 580 ++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 346 +++++++++++++++
7 files changed, 1040 insertions(+)
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/func.sgml b/doc/src/sgml/func.sgml
index 08b07f5..e0a9a90 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23909,6 +23909,47 @@ SELECT collation for ('foo' COLLATE "de_DE");
Undefined objects are identified with <literal>NULL</literal> values.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_parallel_safety</primary>
+ </indexterm>
+ <function>pg_get_parallel_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+ <returnvalue>record</returnvalue>
+ ( <parameter>objid</parameter> <type>oid</type>,
+ <parameter>classid</parameter> <type>oid</type>,
+ <parameter>proparallel</parameter> <type>char</type> )
+ </para>
+ <para>
+ Returns a row containing enough information to uniquely identify the
+ parallel unsafe/restricted table-related objects from which the
+ table's parallel DML safety is determined. The user can use this
+ information during development in order to accurately declare a
+ table's parallel DML safety. Or to identify any problematic objects
+ if a parallel DML fails or behaves unexpectedly. Note that When the
+ use of an object-related parallel unsafe/restricted function is
+ detected, both the function OID and the object OID are returned.
+ <parameter>classid</parameter> is the OID of the system catalog
+ containing the object;
+ <parameter>objid</parameter> is the OID of the object itself.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_max_parallel_hazard</primary>
+ </indexterm>
+ <function>pg_get_max_parallel_hazard</function> ( <type>regclass</type> )
+ <returnvalue>char</returnvalue>
+ </para>
+ <para>
+ Returns the worst parallel DML safety hazard that can be found in the
+ given relation. Users can use this function to do a quick check without
+ caring about specific parallel-related objects.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477ef..3f089f2 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -291,6 +293,28 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
</varlistentry>
<varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="parameter">server_name</replaceable></term>
<listitem>
<para>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index c6d0a35..07182b9 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
@@ -1336,6 +1339,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-paralleldmlsafety">
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab..71c932b 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
AS <replaceable>query</replaceable>
[ WITH [ NO ] DATA ]
</synopsis>
@@ -224,6 +225,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable>query</replaceable></term>
<listitem>
<para>
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000..ca486c6
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,580 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+--
+-- END: setup some tables and data needed by the tests.
+--
+-- 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
+);
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_max_parallel_hazard('para_insert_f1');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names2');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_max_parallel_hazard('names4');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_max_parallel_hazard('temp_names');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('table_check_b');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into table_check_b select * from names;
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('dom_table_u');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d35..46fa6b7 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000..0686cb6
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,346 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+-- 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
+);
+
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+select pg_get_max_parallel_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+select pg_get_max_parallel_hazard('names2');
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+select pg_get_max_parallel_hazard('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+select pg_get_max_parallel_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+select pg_get_max_parallel_hazard('table_check_b');
+-- insert into table_check_b select * from names;
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+select pg_get_max_parallel_hazard('dom_table_u');
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.7.2.windows.1
v6-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v6-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 055879d3a797e2a2f18a70b9e33fa8d613edcf6b Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:28:09 +0800
Subject: [PATCH 1/3] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 ++++++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 65 +++++++++++-----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../modules/test_ddl_deparse/test_ddl_deparse.c | 3 +
25 files changed, 215 insertions(+), 32 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..88fcd57 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index afa830d..7b1152d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -302,6 +302,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -404,7 +405,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -961,6 +963,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1154,6 +1157,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1301,6 +1305,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 50b7a16..ce2ae5a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index bf81f6c..d0a9fea 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e..2151121 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce8820..45aacc8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9..6f25c23 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac..cb5a420 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -925,6 +928,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -943,6 +968,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4184,6 +4210,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4717,6 +4744,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5119,6 +5151,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18614,3 +18649,55 @@ GetAttributeCompression(Oid atttypid, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c..8baebe0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642db..2d77a88 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a8..6bf8787 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3531,6 +3531,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d09..aaed9d1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1284,6 +1285,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14..16c66a8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2703,6 +2704,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea0..09f5f0d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ee90e3..f099795 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -609,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2683,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3276,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3290,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3310,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3331,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3352,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3374,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3396,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4081,6 +4097,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4228,7 +4248,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4237,6 +4257,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5016,7 +5037,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5028,15 +5049,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5048,15 +5070,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5069,15 +5092,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5090,10 +5114,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15563,6 +15588,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16103,6 +16129,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615..ef6200d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1870,6 +1870,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3356,7 +3357,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3506,6 +3508,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7..abca220 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6227,6 +6227,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relproparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6331,7 +6332,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6423,7 +6424,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6476,7 +6477,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6529,7 +6530,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6582,7 +6583,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6633,7 +6634,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6681,7 +6682,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6729,7 +6730,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6776,7 +6777,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6845,6 +6846,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relproparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6900,6 +6902,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relproparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16423,6 +16426,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a..e083593 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..b599759 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729..af280b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342..dcdf6db 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2168,6 +2169,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d..6b532b0 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf..05222fa 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855..5ea225a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5..e1f5678 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.7.2.windows.1
v6-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v6-0002-parallel-SELECT-for-INSERT.patchDownload
From 49f57ad7508e2ee33fade8a1a7358b7c4ec09eaa Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:32:54 +0800
Subject: [PATCH 2/3] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++++
src/backend/executor/execMain.c | 3 ++
src/backend/optimizer/plan/planner.c | 21 ++++-----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4414459..2d68e46 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1015,6 +1015,32 @@ IsInParallelMode(void)
}
/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
+/*
* CommandCounterIncrement
*/
void
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4ba..ea685f0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4e..7736813 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 517712a..7c58c88 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f686..fd3f86b 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887..32b5656 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.7.2.windows.1
v6-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v6-0003-get-parallel-safety-functions.patchDownload
From f3a384416cb832a14c22cf49d493d59c7e003d56 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:38:28 +0800
Subject: [PATCH 3/3] get-parallel-safety-functions
Provide a utility function "pg_get_parallel_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_max_parallel_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 568 ++++++++++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 ++++++
src/backend/utils/cache/typcache.c | 15 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 706 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7c58c88..ecd1597 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all;
+ List *func_oids;
+ PartitionDirectory partition_directory;
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,24 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context);
+static bool target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ bool check_all,
+ char max_interesting,
+ max_parallel_hazard_context *context);
+static bool target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +182,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +656,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +711,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +745,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +762,64 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+
+static safety_object *
+make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+static bool
+parallel_safety_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ cont->func_oids = lappend(cont->func_oids,
+ make_safety_object(func_id, ProcedureRelationId, proparallel));
+ }
+
+ return false;
+}
+
+/* Check parallel unsafe/restricted function in expression */
+static bool
+parallel_safety_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_safety_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_max_parallel_hazard(domain->resulttype, context) &&
+ !context->check_all)
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_safety_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -881,6 +975,478 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+List*
+target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.func_oids = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_all_parallel_hazard_recurse(targetRel, &context);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.func_oids;
+}
+
+
+static bool
+target_rel_all_parallel_hazard_recurse(Relation rel, max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(rel->rd_rel->oid, RelationRelationId,
+ PROPARALLEL_RESTRICTED));
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef)
+ {
+ Node *defaultexpr;
+ defaultexpr = build_column_default(rel, attnum);
+ if (parallel_safety_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_max_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_max_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified relation's
+ * trigger data.
+ */
+static bool
+target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgfoid, ProcedureRelationId, proparallel));
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgoid, TriggerRelationId, proparallel));
+ }
+ }
+
+ return false;
+}
+
+static bool
+index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ Form_pg_index indexStruct;
+ int i;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ if (ii_Expressions != NIL)
+ {
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_safety_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+ }
+
+ if (ii_Predicate != NIL)
+ {
+ if (parallel_safety_walker((Node *) ii_Predicate, context))
+ return true;
+ }
+
+ /*
+ * Check parallel-safety of any index AM support functions.
+ */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+ if (nsupport > 0)
+ {
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+
+ Oid funcOid = index_rel->rd_support[i];
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_index_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any existing index
+ * expressions or index predicate of a specified relation.
+ */
+static bool
+target_rel_index_max_parallel_hazard(Relation rel, max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_max_parallel_hazard(index_rel, ii_Expressions,
+ ii_Predicate, context->check_all,
+ context->max_interesting,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(index_oid, IndexRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified DOMAIN type.
+ * Only any CHECK expressions are examined for parallel-safety.
+ */
+static bool
+target_rel_domain_max_parallel_hazard(Oid typid, max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_safety_walker((Node *) r->check_expr, context) &&
+ !context->check_all)
+ return true;
+
+ /* Add the Constraint itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_domain_constraint_oid(typid, r->name, false),
+ ConstraintRelationId,
+ context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any partitions of a
+ * of a specified relation.
+ */
+static bool
+target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs, *qual;
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_safety_walker((Node *) qual, context))
+ return true;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_safety_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_all_parallel_hazard_recurse(part_rel, context);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ break;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any CHECK expressions or
+ * CHECK constraints related to the specified relation.
+ */
+static List*
+target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ List *temp_objects;
+
+ tupdesc = RelationGetDescr(rel);
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ if (tupdesc->constr != NULL && tupdesc->constr->num_check > 0)
+ {
+ int i;
+
+ ConstrCheck *check = tupdesc->constr->check;
+
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) check_expr, context);
+
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_relation_constraint_oid(rel->rd_rel->oid, check->ccname, true), ConstraintRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+ }
+
+ return false;
+}
+
/*
* is_parallel_allowed_for_modify
*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4d..18eabcd 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_max_parallel_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_max_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_max_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index de96e96..8c0a705 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,21 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+
+List *
+GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4..2f8c528 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_max_parallel_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_max_parallel_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b5656..7ceb750 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a..28ca7d8 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.7.2.windows.1
On Mon, May 31, 2021 at 3:34 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Attaching v6 patchset.
And I registered it in CF https://commitfest.postgresql.org/33/3143/,
comments are welcome.
The latest patchset has some documentation updates. I'd like to
suggest a couple of documentation tweaks (this is mainly just minor
word changes and some extra details):
(1)
doc/src/sgml/ref/create_foreign_table.sgml
doc/src/sgml/ref/create_table.sgml
PARALLEL DML UNSAFE indicates that the data in the table can't be
modified in parallel mode, and this forces a serial execution plan for
DML statements operating on the table. This is the default. PARALLEL
DML RESTRICTED indicates that the data in the table can be modified in
parallel mode, but the modification is restricted to the parallel
group leader. PARALLEL DML SAFE indicates that the data in the table
can be modified in parallel mode without restriction. Note that
PostgreSQL currently does not support data modification by parallel
workers.
Tables should be labeled parallel dml unsafe/restricted if any
parallel unsafe/restricted function could be executed when modifying
the data in the table (e.g., functions in triggers/index
expressions/constraints etc.).
To assist in correctly labeling the parallel DML safety level of a
table, PostgreSQL provides some utility functions that may be used
during application development. Refer to pg_get_parallel_safety() and
pg_get_max_parallel_hazard() for more information.
(2) doc/src/sgml/func.sgml
(i) pg_get_parallel_safety
Returns a row containing enough information to uniquely identify the
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety, or to identify any problematic objects if
parallel DML fails or behaves unexpectedly. Note that when the use of
an object-related parallel unsafe/restricted function is detected,
both the function OID and the object OID are returned. classid is the
OID of the system catalog containing the object; objid is the OID of
the object itself.
(ii) pg_get_max_parallel_hazard
Returns the worst parallel DML safety hazard that can be found in the
given relation:
s safe
r restricted
u unsafe
Users can use this function to do a quick check without caring about
specific parallel-related objects.
---
Also, shouldn't support for "Parallel" be added for table output in
PSQL? (e.g. \dt+)
Regards,
Greg Nancarrow
Fujitsu Australia
From: Greg Nancarrow <gregn4422@gmail.com>
On Mon, May 31, 2021 at 3:34 PM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Attaching v6 patchset.
And I registered it in CF https://commitfest.postgresql.org/33/3143/,
comments are welcome.The latest patchset has some documentation updates. I'd like to suggest a
couple of documentation tweaks (this is mainly just minor word changes and
some extra details):(1)
doc/src/sgml/ref/create_foreign_table.sgml
doc/src/sgml/ref/create_table.sgmlPARALLEL DML UNSAFE indicates that the data in the table can't be modified in
parallel mode, and this forces a serial execution plan for DML statements
operating on the table. This is the default. PARALLEL DML RESTRICTED
indicates that the data in the table can be modified in parallel mode, but the
modification is restricted to the parallel group leader. PARALLEL DML SAFE
indicates that the data in the table can be modified in parallel mode without
restriction. Note that PostgreSQL currently does not support data
modification by parallel workers.Tables should be labeled parallel dml unsafe/restricted if any parallel
unsafe/restricted function could be executed when modifying the data in the
table (e.g., functions in triggers/index expressions/constraints etc.).To assist in correctly labeling the parallel DML safety level of a table,
PostgreSQL provides some utility functions that may be used during
application development. Refer to pg_get_parallel_safety() and
pg_get_max_parallel_hazard() for more information.(2) doc/src/sgml/func.sgml
(i) pg_get_parallel_safety
Returns a row containing enough information to uniquely identify the parallel
unsafe/restricted table-related objects from which the table's parallel DML
safety is determined. The user can use this information during development in
order to accurately declare a table's parallel DML safety, or to identify any
problematic objects if parallel DML fails or behaves unexpectedly. Note that
when the use of an object-related parallel unsafe/restricted function is
detected, both the function OID and the object OID are returned. classid is the
OID of the system catalog containing the object; objid is the OID of the object
itself.(ii) pg_get_max_parallel_hazard
Returns the worst parallel DML safety hazard that can be found in the given
relation:
s safe
r restricted
u unsafe
Users can use this function to do a quick check without caring about specific
parallel-related objects.
Thanks for looking into the doc change, I think your change looks better and
have merged it in the attached patch.
Also, shouldn't support for "Parallel" be added for table output in PSQL? (e.g.
\dt+)
Yeah, I think we should add it and I added it in the attached 0001 patch.
Best regards,
houzj
Attachments:
v7-0004-regression-test-and-doc-updates.patchapplication/octet-stream; name=v7-0004-regression-test-and-doc-updates.patchDownload
From 1789c2460a175c4cc54e716643be77cc96965aa0 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 2 Jun 2021 14:52:03 +0800
Subject: [PATCH] regression-test-and-doc-updates
---
doc/src/sgml/func.sgml | 61 ++
doc/src/sgml/ref/alter_foreign_table.sgml | 13 +
doc/src/sgml/ref/alter_table.sgml | 12 +
doc/src/sgml/ref/create_foreign_table.sgml | 37 ++
doc/src/sgml/ref/create_table.sgml | 38 ++
doc/src/sgml/ref/create_table_as.sgml | 23 +
src/test/regress/expected/alter_table.out | 2 +
src/test/regress/expected/compression_1.out | 9 +
src/test/regress/expected/copy2.out | 1 +
src/test/regress/expected/create_table.out | 14 +
.../regress/expected/create_table_like.out | 8 +
src/test/regress/expected/domain.out | 2 +
src/test/regress/expected/foreign_data.out | 42 ++
src/test/regress/expected/identity.out | 1 +
src/test/regress/expected/inherit.out | 13 +
src/test/regress/expected/insert.out | 12 +
src/test/regress/expected/insert_parallel.out | 580 ++++++++++++++++++
src/test/regress/expected/psql.out | 58 +-
src/test/regress/expected/publication.out | 4 +
.../regress/expected/replica_identity.out | 1 +
src/test/regress/expected/rowsecurity.out | 1 +
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats_ext.out | 1 +
src/test/regress/expected/update.out | 1 +
src/test/regress/output/tablespace.source | 2 +
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 346 +++++++++++
27 files changed, 1259 insertions(+), 27 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 08b07f561e..264db45b03 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23909,6 +23909,67 @@ SELECT collation for ('foo' COLLATE "de_DE");
Undefined objects are identified with <literal>NULL</literal> values.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_parallel_safety</primary>
+ </indexterm>
+ <function>pg_get_parallel_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+ <returnvalue>record</returnvalue>
+ ( <parameter>objid</parameter> <type>oid</type>,
+ <parameter>classid</parameter> <type>oid</type>,
+ <parameter>proparallel</parameter> <type>char</type> )
+ </para>
+ <para>
+ Returns a row containing enough information to uniquely identify the
+ parallel unsafe/restricted table-related objects from which the
+ table's parallel DML safety is determined. The user can use this
+ information during development in order to accurately declare a
+ table's parallel DML safety, or to identify any problematic objects
+ if parallel DML fails or behaves unexpectedly. Note that when the
+ use of an object-related parallel unsafe/restricted function is
+ detected, both the function OID and the object OID are returned.
+ <parameter>classid</parameter> is the OID of the system catalog
+ containing the object;
+ <parameter>objid</parameter> is the OID of the object itself.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_max_parallel_hazard</primary>
+ </indexterm>
+ <function>pg_get_max_parallel_hazard</function> ( <type>regclass</type> )
+ <returnvalue>char</returnvalue>
+ </para>
+ <para>
+ Returns the worst parallel DML safety hazard that can be found in the
+ given relation:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>s</literal> safe
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>r</literal> restricted
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>u</literal> unsafe
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ Users can use this function to do a quick check without caring about
+ specific parallel-related objects.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 7ca03f3ac9..2f0620a283 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -29,6 +29,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -299,6 +301,17 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See the similar form of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>
+ for more details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 939d3fe273..0a6ab86d11 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -37,6 +37,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -1011,6 +1013,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See <link linkend="sql-createtable"><command>CREATE TABLE</command></link> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477efe58..bb5f582d2c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -290,6 +292,41 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table (e.g., functions in triggers/index expression/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_parallel_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_max_parallel_hazard()</function></link> for more information.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">server_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index c6d0a35e50..8e80b09bb3 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
@@ -1336,6 +1339,41 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-paralleldmlsafety">
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table
+ (e.g., functions in triggers/index expressions/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_parallel_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_max_parallel_hazard()</function></link> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab56c..71c932b048 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
AS <replaceable>query</replaceable>
[ WITH [ NO ] DATA ]
</synopsis>
@@ -223,6 +224,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>query</replaceable></term>
<listitem>
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f81bdf513b..e800a218b5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2203,6 +2203,7 @@ alter table test_storage alter column a set storage external;
b | integer | | | 0 | plain | |
Indexes:
"test_storage_idx" btree (b, a)
+Parallel DML: unsafe
\d+ test_storage_idx
Index "public.test_storage_idx"
@@ -4190,6 +4191,7 @@ ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
a | integer | | | | plain | |
Partition key: RANGE (a)
Number of partitions: 0
+Parallel DML: unsafe
-- constraint should be created
\d part_rp
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index aac96037fc..ca76a1d605 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -12,6 +12,7 @@ INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
ERROR: unsupported LZ4 compression method
@@ -51,6 +52,7 @@ SELECT * INTO cmmove1 FROM cmdata;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | text | | | | extended | | |
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmmove1;
pg_column_compression
@@ -138,6 +140,7 @@ CREATE TABLE cmdata2 (f1 int);
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
\d+ cmdata2
@@ -145,6 +148,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
\d+ cmdata2
@@ -152,6 +156,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
--changing column storage should not impact the compression method
--but the data should not be compressed
@@ -162,6 +167,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | pglz | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
\d+ cmdata2
@@ -169,6 +175,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | pglz | |
+Parallel DML: unsafe
INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
SELECT pg_column_compression(f1) FROM cmdata2;
@@ -249,6 +256,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmdata;
pg_column_compression
@@ -263,6 +271,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | | |
+Parallel DML: unsafe
-- test alter compression method for materialized views
ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c64f0719e7..df20bcb97b 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -517,6 +517,7 @@ alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
f1 | integer | | | | plain | |
Check constraints:
"check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+Parallel DML: unsafe
copy check_con_tbl from stdin;
NOTICE: input = {"f1":1}
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index ad89dd05c1..7a3a4bea25 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,6 +505,7 @@ Number of partitions: 0
b | text | | | | extended | |
Partition key: RANGE (((a + 1)), substr(b, 1, 5))
Number of partitions: 0
+Parallel DML: unsafe
INSERT INTO partitioned2 VALUES (1, 'hello');
ERROR: no partition of relation "partitioned2" found for row
@@ -518,6 +519,7 @@ CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO
b | text | | | | extended | |
Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+Parallel DML: unsafe
DROP TABLE partitioned, partitioned2;
-- check reference to partitioned table's rowtype in partition descriptor
@@ -559,6 +561,7 @@ select * from partitioned where partitioned = '(1,2)'::partitioned;
b | integer | | | | plain | |
Partition of: partitioned FOR VALUES IN ('(1,2)')
Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+Parallel DML: unsafe
drop table partitioned;
-- check that dependencies of partition columns are handled correctly
@@ -618,6 +621,7 @@ Partitions: part_null FOR VALUES IN (NULL),
part_p1 FOR VALUES IN (1),
part_p2 FOR VALUES IN (2),
part_p3 FOR VALUES IN (3)
+Parallel DML: unsafe
-- forbidden expressions for partition bound with list partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
@@ -1058,6 +1062,7 @@ drop table test_part_coll_posix;
b | integer | | not null | 1 | plain | |
Partition of: parted FOR VALUES IN ('b')
Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Parallel DML: unsafe
-- Both partition bound and partition key in describe output
\d+ part_c
@@ -1070,6 +1075,7 @@ Partition of: parted FOR VALUES IN ('c')
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
Partition key: RANGE (b)
Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+Parallel DML: unsafe
-- a level-2 partition's constraint will include the parent's expressions
\d+ part_c_1_10
@@ -1080,6 +1086,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
b | integer | | not null | 0 | plain | |
Partition of: part_c FOR VALUES FROM (1) TO (10)
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Parallel DML: unsafe
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
@@ -1114,6 +1121,7 @@ CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MI
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+Parallel DML: unsafe
DROP TABLE unbounded_range_part;
CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
@@ -1126,6 +1134,7 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALU
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Parallel DML: unsafe
CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
\d+ range_parted4_2
@@ -1137,6 +1146,7 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Parallel DML: unsafe
CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
\d+ range_parted4_3
@@ -1148,6 +1158,7 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Parallel DML: unsafe
DROP TABLE range_parted4;
-- user-defined operator class in partition key
@@ -1184,6 +1195,7 @@ SELECT obj_description('parted_col_comment'::regclass);
b | text | | | | extended | |
Partition key: LIST (a)
Number of partitions: 0
+Parallel DML: unsafe
DROP TABLE parted_col_comment;
-- list partitioning on array type column
@@ -1196,6 +1208,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
a | integer[] | | | | extended | |
Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+Parallel DML: unsafe
DROP TABLE arrlp;
-- partition on boolean column
@@ -1210,6 +1223,7 @@ create table boolspart_f partition of boolspart for values in (false);
Partition key: LIST (a)
Partitions: boolspart_f FOR VALUES IN (false),
boolspart_t FOR VALUES IN (true)
+Parallel DML: unsafe
drop table boolspart;
-- partitions mixing temporary and permanent relations
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 4dc5e6aa5f..af8de78bdd 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -333,6 +333,7 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
a | text | | not null | | main | |
b | text | | | | extended | |
c | text | | | | external | |
+Parallel DML: unsafe
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@@ -342,6 +343,7 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
a | text | | not null | | extended | | A
b | text | | | | extended | | B
c | text | | | | extended | | C
+Parallel DML: unsafe
CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -356,6 +358,7 @@ NOTICE: merging constraint "ctlt1_a_check" with inherited definition
Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
description
@@ -378,6 +381,7 @@ Check constraints:
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1,
ctlt3
+Parallel DML: unsafe
CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -395,6 +399,7 @@ Check constraints:
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
description
@@ -418,6 +423,7 @@ Check constraints:
Statistics objects:
"public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
"public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
+Parallel DML: unsafe
SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
relname | objsubid | description
@@ -458,6 +464,7 @@ Check constraints:
Statistics objects:
"public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
"public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
+Parallel DML: unsafe
DROP TABLE public.pg_attrdef;
-- Check that LIKE isn't confused when new table masks the old, either
@@ -480,6 +487,7 @@ Check constraints:
Statistics objects:
"ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
"ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
+Parallel DML: unsafe
ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..2419d96a33 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -276,6 +276,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
WHERE (dcomptable.d1).i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
@@ -413,6 +414,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
WHERE dcomptable.d1[1].i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 5385f98a0f..4f50410f39 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -731,6 +731,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
\det+
List of foreign tables
@@ -852,6 +853,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- can't change the column type if it's used elsewhere
CREATE TABLE use_ft1_column_type (x ft1);
@@ -1390,6 +1392,7 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1401,6 +1404,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2;
\d+ fd_pt1
@@ -1410,6 +1414,7 @@ DROP FOREIGN TABLE ft2;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Parallel DML: unsafe
CREATE FOREIGN TABLE ft2 (
c1 integer NOT NULL,
@@ -1425,6 +1430,7 @@ CREATE FOREIGN TABLE ft2 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
\d+ fd_pt1
@@ -1435,6 +1441,7 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1446,6 +1453,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
CREATE TABLE ct3() INHERITS(ft2);
CREATE FOREIGN TABLE ft3 (
@@ -1469,6 +1477,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1478,6 +1487,7 @@ Child tables: ct3,
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1488,6 +1498,7 @@ Inherits: ft2
c3 | date | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- add attributes recursively
ALTER TABLE fd_pt1 ADD COLUMN c4 integer;
@@ -1508,6 +1519,7 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1526,6 +1538,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1540,6 +1553,7 @@ Child tables: ct3,
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1555,6 +1569,7 @@ Inherits: ft2
c8 | integer | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- alter attributes recursively
ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0;
@@ -1582,6 +1597,7 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
c7 | integer | | | | plain | |
c8 | text | | | | external | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1600,6 +1616,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- drop attributes recursively
ALTER TABLE fd_pt1 DROP COLUMN c4;
@@ -1615,6 +1632,7 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1628,6 +1646,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- add constraints recursively
ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT;
@@ -1655,6 +1674,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1670,6 +1690,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2; -- ERROR
ERROR: cannot drop foreign table ft2 because other objects depend on it
@@ -1702,6 +1723,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1715,6 +1737,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- drop constraints recursively
ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE;
@@ -1732,6 +1755,7 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1746,6 +1770,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- VALIDATE CONSTRAINT need do nothing on foreign tables
ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
@@ -1759,6 +1784,7 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1773,6 +1799,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- changes name of an attribute recursively
ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1;
@@ -1790,6 +1817,7 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1804,6 +1832,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR
@@ -1853,6 +1882,7 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1865,6 +1895,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- partition cannot have additional columns
DROP FOREIGN TABLE fd_pt2_1;
@@ -1884,6 +1915,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c4 | character(1) | | | | | extended | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
@@ -1898,6 +1930,7 @@ DROP FOREIGN TABLE fd_pt2_1;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
CREATE FOREIGN TABLE fd_pt2_1 (
c1 integer NOT NULL,
@@ -1913,6 +1946,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- no attach partition validation occurs for foreign tables
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
@@ -1925,6 +1959,7 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1937,6 +1972,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot add column to a partition
ALTER TABLE fd_pt2_1 ADD c4 char;
@@ -1953,6 +1989,7 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1967,6 +2004,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot drop inherited NOT NULL constraint from a partition
ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
@@ -1983,6 +2021,7 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1995,6 +2034,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: column "c2" in child table must be marked NOT NULL
@@ -2013,6 +2053,7 @@ Partition key: LIST (c1)
Check constraints:
"fd_pt2chk1" CHECK (c1 > 0)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -2025,6 +2066,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: child table is missing constraint "fd_pt2chk1"
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7..da24c16d09 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -506,6 +506,7 @@ TABLE itest8;
f3 | integer | | not null | generated by default as identity | plain | |
f4 | bigint | | not null | generated always as identity | plain | |
f5 | bigint | | | | plain | |
+Parallel DML: unsafe
\d itest8_f2_seq
Sequence "public.itest8_f2_seq"
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 06f44287bc..33a216ea4d 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1059,6 +1059,7 @@ ALTER TABLE inhts RENAME d TO dd;
dd | integer | | | | plain | |
Inherits: inht1,
inhs1
+Parallel DML: unsafe
DROP TABLE inhts;
-- Test for renaming in diamond inheritance
@@ -1079,6 +1080,7 @@ ALTER TABLE inht1 RENAME aa TO aaa;
z | integer | | | | plain | |
Inherits: inht2,
inht3
+Parallel DML: unsafe
CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
NOTICE: merging multiple inherited definitions of column "b"
@@ -1096,6 +1098,7 @@ ERROR: cannot rename inherited column "b"
d | integer | | | | plain | |
Inherits: inht2,
inhs1
+Parallel DML: unsafe
WITH RECURSIVE r AS (
SELECT 'inht1'::regclass AS inhrelid
@@ -1142,6 +1145,7 @@ CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
Indexes:
"test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
Child tables: test_constraints_inh
+Parallel DML: unsafe
ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
\d+ test_constraints
@@ -1152,6 +1156,7 @@ ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Child tables: test_constraints_inh
+Parallel DML: unsafe
\d+ test_constraints_inh
Table "public.test_constraints_inh"
@@ -1161,6 +1166,7 @@ Child tables: test_constraints_inh
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Inherits: test_constraints
+Parallel DML: unsafe
DROP TABLE test_constraints_inh;
DROP TABLE test_constraints;
@@ -1177,6 +1183,7 @@ CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
Indexes:
"test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
\d+ test_ex_constraints
@@ -1185,6 +1192,7 @@ ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
\d+ test_ex_constraints_inh
Table "public.test_ex_constraints_inh"
@@ -1192,6 +1200,7 @@ Child tables: test_ex_constraints_inh
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Inherits: test_ex_constraints
+Parallel DML: unsafe
DROP TABLE test_ex_constraints_inh;
DROP TABLE test_ex_constraints;
@@ -1208,6 +1217,7 @@ Indexes:
"test_primary_constraints_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Parallel DML: unsafe
\d+ test_foreign_constraints
Table "public.test_foreign_constraints"
@@ -1217,6 +1227,7 @@ Referenced by:
Foreign-key constraints:
"test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
\d+ test_foreign_constraints
@@ -1225,6 +1236,7 @@ ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
\d+ test_foreign_constraints_inh
Table "public.test_foreign_constraints_inh"
@@ -1232,6 +1244,7 @@ Child tables: test_foreign_constraints_inh
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Inherits: test_foreign_constraints
+Parallel DML: unsafe
DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..cb8cd958aa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -177,6 +177,7 @@ Rules:
irule3 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
new.f2
+Parallel DML: unsafe
drop table inserttest2;
drop table inserttest;
@@ -482,6 +483,7 @@ Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
part_null FOR VALUES IN (NULL),
part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
part_default DEFAULT, PARTITIONED
+Parallel DML: unsafe
-- cleanup
drop table range_parted, list_parted;
@@ -497,6 +499,7 @@ create table part_default partition of list_parted default;
a | integer | | | | plain | |
Partition of: list_parted DEFAULT
No partition constraint
+Parallel DML: unsafe
insert into part_default values (null);
insert into part_default values (1);
@@ -888,6 +891,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Parallel DML: unsafe
\d+ mcrparted1_lt_b
Table "public.mcrparted1_lt_b"
@@ -897,6 +901,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+Parallel DML: unsafe
\d+ mcrparted2_b
Table "public.mcrparted2_b"
@@ -906,6 +911,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+Parallel DML: unsafe
\d+ mcrparted3_c_to_common
Table "public.mcrparted3_c_to_common"
@@ -915,6 +921,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+Parallel DML: unsafe
\d+ mcrparted4_common_lt_0
Table "public.mcrparted4_common_lt_0"
@@ -924,6 +931,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+Parallel DML: unsafe
\d+ mcrparted5_common_0_to_10
Table "public.mcrparted5_common_0_to_10"
@@ -933,6 +941,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+Parallel DML: unsafe
\d+ mcrparted6_common_ge_10
Table "public.mcrparted6_common_ge_10"
@@ -942,6 +951,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+Parallel DML: unsafe
\d+ mcrparted7_gt_common_lt_d
Table "public.mcrparted7_gt_common_lt_d"
@@ -951,6 +961,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+Parallel DML: unsafe
\d+ mcrparted8_ge_d
Table "public.mcrparted8_ge_d"
@@ -960,6 +971,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+Parallel DML: unsafe
insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
('comm', -10), ('common', -10), ('common', 0), ('common', 10),
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..ca486c63e6
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,580 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+--
+-- END: setup some tables and data needed by the tests.
+--
+-- 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
+);
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_max_parallel_hazard('para_insert_f1');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names2');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_max_parallel_hazard('names4');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_max_parallel_hazard('temp_names');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('table_check_b');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into table_check_b select * from names;
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('dom_table_u');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1901673622 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2818,6 +2818,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2825,6 +2826,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\set HIDE_TABLEAM off
\d+ tbl_heap_psql
@@ -2834,6 +2836,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap_psql
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2842,50 +2845,51 @@ Access method: heap_psql
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap
+Parallel DML: unsafe
-- AM is displayed for tables, indexes and materialized views.
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | | unsafe | 0 bytes |
(4 rows)
\dt+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+---------------+-------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(2 rows)
\dm+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(1 row)
-- But not for views and sequences.
\dv+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+----------------+------+----------------------+-------------+---------+-------------
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+----------------+------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(1 row)
\set HIDE_TABLEAM on
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(4 rows)
RESET ROLE;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..314ec05dc1 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -83,6 +83,7 @@ Indexes:
"testpub_tbl2_pkey" PRIMARY KEY, btree (id)
Publications:
"testpub_foralltables"
+Parallel DML: unsafe
\dRp+ testpub_foralltables
Publication testpub_foralltables
@@ -196,6 +197,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\d+ testpub_tbl1
Table "public.testpub_tbl1"
@@ -209,6 +211,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\dRp+ testpub_default
Publication testpub_default
@@ -234,6 +237,7 @@ Indexes:
Publications:
"testpib_ins_trunct"
"testpub_fortbl"
+Parallel DML: unsafe
-- permissions
SET ROLE regress_publication_user2;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..0f8718f2a4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -171,6 +171,7 @@ Indexes:
"test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
"test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
Replica Identity: FULL
+Parallel DML: unsafe
ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 367ecace47..369bea2e65 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -958,6 +958,7 @@ Policies:
Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
part_document_nonfiction FOR VALUES FROM (99) TO (100),
part_document_satire FOR VALUES FROM (55) TO (56)
+Parallel DML: unsafe
SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..cad4be0a8b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3155,6 +3155,7 @@ Rules:
r3 AS
ON DELETE TO rules_src DO
NOTIFY rules_src_deletion
+Parallel DML: unsafe
--
-- Ensure an aliased target relation for insert is correctly deparsed.
@@ -3183,6 +3184,7 @@ Rules:
r5 AS
ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text
WHERE trgt.f1 = new.f1
+Parallel DML: unsafe
--
-- Also check multiassignment deparsing.
@@ -3206,6 +3208,7 @@ Rules:
WHERE trgt.f1 = new.f1
RETURNING new.f1,
new.f2
+Parallel DML: unsafe
drop table rule_t1, rule_dest;
--
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c214d8dfc..c94eb3293e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -145,6 +145,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
b | integer | | | | plain | |
Statistics objects:
"public"."ab1_a_b_stats" ON a, b FROM ab1
+Parallel DML: unsafe
-- partial analyze doesn't build stats either
ANALYZE ab1 (a);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index c809f88f54..3fcd8e10f3 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -753,6 +753,7 @@ create table part_def partition of range_parted default;
e | character varying | | | | extended | |
Partition of: range_parted DEFAULT
Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
+Parallel DML: unsafe
insert into range_parted values ('c', 9);
-- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..11a750b58d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -339,6 +339,7 @@ Indexes:
"part_a_idx" btree (a), tablespace "regress_tblspace"
Partitions: testschema.part1 FOR VALUES IN (1),
testschema.part2 FOR VALUES IN (2)
+Parallel DML: unsafe
\d testschema.part1
Table "testschema.part1"
@@ -358,6 +359,7 @@ Partition of: testschema.part FOR VALUES IN (1)
Partition constraint: ((a IS NOT NULL) AND (a = 1))
Indexes:
"part1_a_idx" btree (a), tablespace "regress_tblspace"
+Parallel DML: unsafe
\d testschema.part_a_idx
Partitioned index "testschema.part_a_idx"
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..0686cb6646
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,346 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+-- 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
+);
+
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+select pg_get_max_parallel_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+select pg_get_max_parallel_hazard('names2');
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+select pg_get_max_parallel_hazard('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+select pg_get_max_parallel_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+select pg_get_max_parallel_hazard('table_check_b');
+-- insert into table_check_b select * from names;
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+select pg_get_max_parallel_hazard('dom_table_u');
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
v7-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v7-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 57e72e841f87ba4380f1abef88e76ed4609e4348 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Wed, 2 Jun 2021 11:14:26 +0800
Subject: [PATCH] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 ++++++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 65 +++++++++++-----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 69 +++++++++++++++--
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../modules/test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 277 insertions(+), 39 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..88fcd57 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index afa830d..7b1152d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -302,6 +302,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -404,7 +405,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -961,6 +963,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1154,6 +1157,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1301,6 +1305,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 50b7a16..ce2ae5a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index bf81f6c..d0a9fea 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e..2151121 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce8820..45aacc8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9..6f25c23 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac..5d14b1e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -925,6 +928,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -943,6 +968,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4184,6 +4210,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4717,6 +4744,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5119,6 +5151,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18614,3 +18649,55 @@ GetAttributeCompression(Oid atttypid, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c..8baebe0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642db..2d77a88 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a8..6bf8787 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3531,6 +3531,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d09..aaed9d1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1284,6 +1285,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14..16c66a8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2703,6 +2704,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea0..09f5f0d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ee90e3..f099795 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -609,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2683,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3276,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3290,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3310,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3331,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3352,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3374,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3396,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4081,6 +4097,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4228,7 +4248,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4237,6 +4257,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5016,7 +5037,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5028,15 +5049,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5048,15 +5070,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5069,15 +5092,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5090,10 +5114,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15563,6 +15588,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16103,6 +16129,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615..ef6200d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1870,6 +1870,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3356,7 +3357,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3506,6 +3508,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7..abca220 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6227,6 +6227,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relproparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6331,7 +6332,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6423,7 +6424,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6476,7 +6477,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6529,7 +6530,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6582,7 +6583,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6633,7 +6634,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6681,7 +6682,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6729,7 +6730,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6776,7 +6777,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6845,6 +6846,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relproparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6900,6 +6902,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relproparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16423,6 +16426,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a..e083593 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 195f8d8..f6bab2f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1656,6 +1656,7 @@ describeOneTableDetails(const char *schemaname,
char *reloftype;
char relpersistence;
char relreplident;
+ char relparalleldml;
char *relam;
} tableinfo;
bool show_column_details = false;
@@ -1669,7 +1670,25 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
- if (pset.sversion >= 120000)
+ if (pset.sversion >= 140000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+ "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+ "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+ "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+ "c.relpersistence, c.relreplident, am.amname, c.relparalleldml\n"
+ "FROM pg_catalog.pg_class c\n "
+ "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+ "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+ "WHERE c.oid = '%s';",
+ (verbose ?
+ "pg_catalog.array_to_string(c.reloptions || "
+ "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+ : "''"),
+ oid);
+ }
+ else if (pset.sversion >= 120000)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1872,8 @@ describeOneTableDetails(const char *schemaname,
(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
else
tableinfo.relam = NULL;
+ tableinfo.relparalleldml = (pset.sversion >= 140000) ?
+ *(PQgetvalue(res, 0, 15)) : 0;
PQclear(res);
res = NULL;
@@ -3630,6 +3651,20 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
printTableAddFooter(&cont, buf.data);
}
+
+ if (verbose &&
+ (tableinfo.relkind == RELKIND_RELATION ||
+ tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+ tableinfo.relkind == RELKIND_FOREIGN_TABLE) &&
+ tableinfo.relparalleldml != 0)
+ {
+ printfPQExpBuffer(&buf, _("Parallel DML: %s"),
+ tableinfo.relparalleldml == 'u' ? "unsafe" :
+ tableinfo.relparalleldml == 'r' ? "restricted" :
+ tableinfo.relparalleldml == 's' ? "safe" :
+ "???");
+ printTableAddFooter(&cont, buf.data);
+ }
}
/* reloptions, if verbose */
@@ -4005,7 +4040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
PGresult *res;
printQueryOpt myopt = pset.popt;
int cols_so_far;
- bool translate_columns[] = {false, false, true, false, false, false, false, false, false};
+ bool translate_columns[] = {false, false, true, false, false, false, false, false, false, false};
/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
@@ -4073,22 +4108,42 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("unlogged"),
gettext_noop("Persistence"));
translate_columns[cols_so_far] = true;
+ cols_so_far++;
}
/*
- * We don't bother to count cols_so_far below here, as there's no need
- * to; this might change with future additions to the output columns.
- */
-
- /*
* Access methods exist for tables, materialized views and indexes.
* This has been introduced in PostgreSQL 12 for tables.
*/
if (pset.sversion >= 120000 && !pset.hide_tableam &&
(showTables || showMatViews || showIndexes))
+ {
appendPQExpBuffer(&buf,
",\n am.amname as \"%s\"",
gettext_noop("Access method"));
+ cols_so_far++;
+ }
+
+ /*
+ * Show whether the data in the relation is unsafe('u'),
+ * restricted('r'), or safe('s') can be modified in parallel mode.
+ * This has been introduced in PostgreSQL 15 for tables.
+ */
+ if (pset.sversion >= 140000)
+ {
+ appendPQExpBuffer(&buf,
+ ",\n CASE c.relparalleldml WHEN 'u' THEN '%s' WHEN 'r' THEN '%s' WHEN 's' THEN '%s' END as \"%s\"",
+ gettext_noop("unsafe"),
+ gettext_noop("restricted"),
+ gettext_noop("safe"),
+ gettext_noop("Parallel DML"));
+ translate_columns[cols_so_far] = true;
+ }
+
+ /*
+ * We don't bother to count cols_so_far below here, as there's no need
+ * to; this might change with future additions to the output columns.
+ */
/*
* As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..b599759 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729..af280b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342..dcdf6db 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2168,6 +2169,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d..6b532b0 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf..05222fa 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855..5ea225a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5..e1f5678 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.7.2.windows.1
v7-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v7-0002-parallel-SELECT-for-INSERT.patchDownload
From 49f57ad7508e2ee33fade8a1a7358b7c4ec09eaa Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:32:54 +0800
Subject: [PATCH 2/3] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++++
src/backend/executor/execMain.c | 3 ++
src/backend/optimizer/plan/planner.c | 21 ++++-----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4414459..2d68e46 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1015,6 +1015,32 @@ IsInParallelMode(void)
}
/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
+/*
* CommandCounterIncrement
*/
void
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4ba..ea685f0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4e..7736813 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 517712a..7c58c88 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f686..fd3f86b 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887..32b5656 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.7.2.windows.1
v7-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v7-0003-get-parallel-safety-functions.patchDownload
From f3a384416cb832a14c22cf49d493d59c7e003d56 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:38:28 +0800
Subject: [PATCH 3/3] get-parallel-safety-functions
Provide a utility function "pg_get_parallel_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_max_parallel_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 568 ++++++++++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 ++++++
src/backend/utils/cache/typcache.c | 15 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 706 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7c58c88..ecd1597 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all;
+ List *func_oids;
+ PartitionDirectory partition_directory;
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,24 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context);
+static bool target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ bool check_all,
+ char max_interesting,
+ max_parallel_hazard_context *context);
+static bool target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +182,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +656,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +711,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +745,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +762,64 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+
+static safety_object *
+make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+static bool
+parallel_safety_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ cont->func_oids = lappend(cont->func_oids,
+ make_safety_object(func_id, ProcedureRelationId, proparallel));
+ }
+
+ return false;
+}
+
+/* Check parallel unsafe/restricted function in expression */
+static bool
+parallel_safety_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_safety_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_max_parallel_hazard(domain->resulttype, context) &&
+ !context->check_all)
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_safety_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -881,6 +975,478 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+List*
+target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.func_oids = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_all_parallel_hazard_recurse(targetRel, &context);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.func_oids;
+}
+
+
+static bool
+target_rel_all_parallel_hazard_recurse(Relation rel, max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(rel->rd_rel->oid, RelationRelationId,
+ PROPARALLEL_RESTRICTED));
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef)
+ {
+ Node *defaultexpr;
+ defaultexpr = build_column_default(rel, attnum);
+ if (parallel_safety_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_max_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_max_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified relation's
+ * trigger data.
+ */
+static bool
+target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgfoid, ProcedureRelationId, proparallel));
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgoid, TriggerRelationId, proparallel));
+ }
+ }
+
+ return false;
+}
+
+static bool
+index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ Form_pg_index indexStruct;
+ int i;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ if (ii_Expressions != NIL)
+ {
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_safety_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+ }
+
+ if (ii_Predicate != NIL)
+ {
+ if (parallel_safety_walker((Node *) ii_Predicate, context))
+ return true;
+ }
+
+ /*
+ * Check parallel-safety of any index AM support functions.
+ */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+ if (nsupport > 0)
+ {
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+
+ Oid funcOid = index_rel->rd_support[i];
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_index_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any existing index
+ * expressions or index predicate of a specified relation.
+ */
+static bool
+target_rel_index_max_parallel_hazard(Relation rel, max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_max_parallel_hazard(index_rel, ii_Expressions,
+ ii_Predicate, context->check_all,
+ context->max_interesting,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(index_oid, IndexRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified DOMAIN type.
+ * Only any CHECK expressions are examined for parallel-safety.
+ */
+static bool
+target_rel_domain_max_parallel_hazard(Oid typid, max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_safety_walker((Node *) r->check_expr, context) &&
+ !context->check_all)
+ return true;
+
+ /* Add the Constraint itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_domain_constraint_oid(typid, r->name, false),
+ ConstraintRelationId,
+ context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any partitions of a
+ * of a specified relation.
+ */
+static bool
+target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs, *qual;
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_safety_walker((Node *) qual, context))
+ return true;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_safety_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_all_parallel_hazard_recurse(part_rel, context);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ break;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any CHECK expressions or
+ * CHECK constraints related to the specified relation.
+ */
+static List*
+target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ List *temp_objects;
+
+ tupdesc = RelationGetDescr(rel);
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ if (tupdesc->constr != NULL && tupdesc->constr->num_check > 0)
+ {
+ int i;
+
+ ConstrCheck *check = tupdesc->constr->check;
+
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) check_expr, context);
+
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_relation_constraint_oid(rel->rd_rel->oid, check->ccname, true), ConstraintRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+ }
+
+ return false;
+}
+
/*
* is_parallel_allowed_for_modify
*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4d..18eabcd 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_max_parallel_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_max_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_max_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index de96e96..8c0a705 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,21 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+
+List *
+GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4..2f8c528 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_max_parallel_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_max_parallel_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b5656..7ceb750 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a..28ca7d8 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.7.2.windows.1
From: houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com>
Thanks for looking into the doc change, I think your change looks better and
have merged it in the attached patch.Also, shouldn't support for "Parallel" be added for table output in PSQL? (e.g.
\dt+)Yeah, I think we should add it and I added it in the attached 0001 patch.
Oops, forgot to update the regression test in contrib/.
Attaching new version patchset with this fix.
Best regards,
houzj
Attachments:
v8-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v8-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 57e72e841f87ba4380f1abef88e76ed4609e4348 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Wed, 2 Jun 2021 11:14:26 +0800
Subject: [PATCH] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 ++++++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 65 +++++++++++-----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 69 +++++++++++++++--
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../modules/test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 277 insertions(+), 39 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..88fcd57 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index afa830d..7b1152d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -302,6 +302,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -404,7 +405,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -961,6 +963,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1154,6 +1157,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1301,6 +1305,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 50b7a16..ce2ae5a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index bf81f6c..d0a9fea 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e..2151121 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce8820..45aacc8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9..6f25c23 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac..5d14b1e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -925,6 +928,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -943,6 +968,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4184,6 +4210,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4717,6 +4744,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5119,6 +5151,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18614,3 +18649,55 @@ GetAttributeCompression(Oid atttypid, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c..8baebe0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642db..2d77a88 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a8..6bf8787 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3531,6 +3531,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d09..aaed9d1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1284,6 +1285,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14..16c66a8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2703,6 +2704,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea0..09f5f0d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ee90e3..f099795 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -609,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2683,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3276,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3290,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3310,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3331,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3352,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3374,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3396,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4081,6 +4097,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4228,7 +4248,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4237,6 +4257,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5016,7 +5037,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5028,15 +5049,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5048,15 +5070,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5069,15 +5092,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5090,10 +5114,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15563,6 +15588,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16103,6 +16129,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615..ef6200d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1870,6 +1870,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3356,7 +3357,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3506,6 +3508,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7..abca220 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6227,6 +6227,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relproparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6331,7 +6332,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6423,7 +6424,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6476,7 +6477,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6529,7 +6530,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6582,7 +6583,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6633,7 +6634,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6681,7 +6682,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6729,7 +6730,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6776,7 +6777,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6845,6 +6846,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relproparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6900,6 +6902,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relproparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16423,6 +16426,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a..e083593 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 195f8d8..f6bab2f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1656,6 +1656,7 @@ describeOneTableDetails(const char *schemaname,
char *reloftype;
char relpersistence;
char relreplident;
+ char relparalleldml;
char *relam;
} tableinfo;
bool show_column_details = false;
@@ -1669,7 +1670,25 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
- if (pset.sversion >= 120000)
+ if (pset.sversion >= 140000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+ "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+ "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+ "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+ "c.relpersistence, c.relreplident, am.amname, c.relparalleldml\n"
+ "FROM pg_catalog.pg_class c\n "
+ "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+ "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+ "WHERE c.oid = '%s';",
+ (verbose ?
+ "pg_catalog.array_to_string(c.reloptions || "
+ "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+ : "''"),
+ oid);
+ }
+ else if (pset.sversion >= 120000)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1872,8 @@ describeOneTableDetails(const char *schemaname,
(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
else
tableinfo.relam = NULL;
+ tableinfo.relparalleldml = (pset.sversion >= 140000) ?
+ *(PQgetvalue(res, 0, 15)) : 0;
PQclear(res);
res = NULL;
@@ -3630,6 +3651,20 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
printTableAddFooter(&cont, buf.data);
}
+
+ if (verbose &&
+ (tableinfo.relkind == RELKIND_RELATION ||
+ tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+ tableinfo.relkind == RELKIND_FOREIGN_TABLE) &&
+ tableinfo.relparalleldml != 0)
+ {
+ printfPQExpBuffer(&buf, _("Parallel DML: %s"),
+ tableinfo.relparalleldml == 'u' ? "unsafe" :
+ tableinfo.relparalleldml == 'r' ? "restricted" :
+ tableinfo.relparalleldml == 's' ? "safe" :
+ "???");
+ printTableAddFooter(&cont, buf.data);
+ }
}
/* reloptions, if verbose */
@@ -4005,7 +4040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
PGresult *res;
printQueryOpt myopt = pset.popt;
int cols_so_far;
- bool translate_columns[] = {false, false, true, false, false, false, false, false, false};
+ bool translate_columns[] = {false, false, true, false, false, false, false, false, false, false};
/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
@@ -4073,22 +4108,42 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("unlogged"),
gettext_noop("Persistence"));
translate_columns[cols_so_far] = true;
+ cols_so_far++;
}
/*
- * We don't bother to count cols_so_far below here, as there's no need
- * to; this might change with future additions to the output columns.
- */
-
- /*
* Access methods exist for tables, materialized views and indexes.
* This has been introduced in PostgreSQL 12 for tables.
*/
if (pset.sversion >= 120000 && !pset.hide_tableam &&
(showTables || showMatViews || showIndexes))
+ {
appendPQExpBuffer(&buf,
",\n am.amname as \"%s\"",
gettext_noop("Access method"));
+ cols_so_far++;
+ }
+
+ /*
+ * Show whether the data in the relation is unsafe('u'),
+ * restricted('r'), or safe('s') can be modified in parallel mode.
+ * This has been introduced in PostgreSQL 15 for tables.
+ */
+ if (pset.sversion >= 140000)
+ {
+ appendPQExpBuffer(&buf,
+ ",\n CASE c.relparalleldml WHEN 'u' THEN '%s' WHEN 'r' THEN '%s' WHEN 's' THEN '%s' END as \"%s\"",
+ gettext_noop("unsafe"),
+ gettext_noop("restricted"),
+ gettext_noop("safe"),
+ gettext_noop("Parallel DML"));
+ translate_columns[cols_so_far] = true;
+ }
+
+ /*
+ * We don't bother to count cols_so_far below here, as there's no need
+ * to; this might change with future additions to the output columns.
+ */
/*
* As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..b599759 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729..af280b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342..dcdf6db 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2168,6 +2169,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d..6b532b0 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf..05222fa 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855..5ea225a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5..e1f5678 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.7.2.windows.1
v8-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v8-0002-parallel-SELECT-for-INSERT.patchDownload
From 49f57ad7508e2ee33fade8a1a7358b7c4ec09eaa Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:32:54 +0800
Subject: [PATCH 2/3] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++++
src/backend/executor/execMain.c | 3 ++
src/backend/optimizer/plan/planner.c | 21 ++++-----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4414459..2d68e46 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1015,6 +1015,32 @@ IsInParallelMode(void)
}
/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
+/*
* CommandCounterIncrement
*/
void
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4ba..ea685f0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4e..7736813 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 517712a..7c58c88 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f686..fd3f86b 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887..32b5656 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.7.2.windows.1
v8-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v8-0003-get-parallel-safety-functions.patchDownload
From f3a384416cb832a14c22cf49d493d59c7e003d56 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:38:28 +0800
Subject: [PATCH 3/3] get-parallel-safety-functions
Provide a utility function "pg_get_parallel_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_max_parallel_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 568 ++++++++++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 ++++++
src/backend/utils/cache/typcache.c | 15 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 706 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7c58c88..ecd1597 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all;
+ List *func_oids;
+ PartitionDirectory partition_directory;
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,24 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context);
+static bool target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ bool check_all,
+ char max_interesting,
+ max_parallel_hazard_context *context);
+static bool target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +182,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +656,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +711,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.func_oids = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +745,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +762,64 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+
+static safety_object *
+make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+static bool
+parallel_safety_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ cont->func_oids = lappend(cont->func_oids,
+ make_safety_object(func_id, ProcedureRelationId, proparallel));
+ }
+
+ return false;
+}
+
+/* Check parallel unsafe/restricted function in expression */
+static bool
+parallel_safety_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_safety_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_max_parallel_hazard(domain->resulttype, context) &&
+ !context->check_all)
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_safety_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -881,6 +975,478 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+List*
+target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.func_oids = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_all_parallel_hazard_recurse(targetRel, &context);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.func_oids;
+}
+
+
+static bool
+target_rel_all_parallel_hazard_recurse(Relation rel, max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(rel->rd_rel->oid, RelationRelationId,
+ PROPARALLEL_RESTRICTED));
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef)
+ {
+ Node *defaultexpr;
+ defaultexpr = build_column_default(rel, attnum);
+ if (parallel_safety_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_max_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_max_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified relation's
+ * trigger data.
+ */
+static bool
+target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgfoid, ProcedureRelationId, proparallel));
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(tgoid, TriggerRelationId, proparallel));
+ }
+ }
+
+ return false;
+}
+
+static bool
+index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions, List *ii_Predicate,
+ bool check_all, char max_interesting,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ Form_pg_index indexStruct;
+ int i;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ if (ii_Expressions != NIL)
+ {
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_safety_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+ }
+
+ if (ii_Predicate != NIL)
+ {
+ if (parallel_safety_walker((Node *) ii_Predicate, context))
+ return true;
+ }
+
+ /*
+ * Check parallel-safety of any index AM support functions.
+ */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+ if (nsupport > 0)
+ {
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+
+ Oid funcOid = index_rel->rd_support[i];
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_index_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any existing index
+ * expressions or index predicate of a specified relation.
+ */
+static bool
+target_rel_index_max_parallel_hazard(Relation rel, max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_max_parallel_hazard(index_rel, ii_Expressions,
+ ii_Predicate, context->check_all,
+ context->max_interesting,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(index_oid, IndexRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified DOMAIN type.
+ * Only any CHECK expressions are examined for parallel-safety.
+ */
+static bool
+target_rel_domain_max_parallel_hazard(Oid typid, max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_safety_walker((Node *) r->check_expr, context) &&
+ !context->check_all)
+ return true;
+
+ /* Add the Constraint itself to the objects list */
+ else if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_domain_constraint_oid(typid, r->name, false),
+ ConstraintRelationId,
+ context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any partitions of a
+ * of a specified relation.
+ */
+static bool
+target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs, *qual;
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_safety_walker((Node *) qual, context))
+ return true;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(funcOid, ProcedureRelationId, proparallel));
+ }
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_safety_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_all_parallel_hazard_recurse(part_rel, context);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ break;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any CHECK expressions or
+ * CHECK constraints related to the specified relation.
+ */
+static List*
+target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ List *temp_objects;
+
+ tupdesc = RelationGetDescr(rel);
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ if (tupdesc->constr != NULL && tupdesc->constr->num_check > 0)
+ {
+ int i;
+
+ ConstrCheck *check = tupdesc->constr->check;
+
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->func_oids;
+ context->func_oids = NIL;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_safety_walker((Node *) check_expr, context);
+
+ if (context->func_oids != NIL)
+ {
+ context->func_oids = lappend(context->func_oids,
+ make_safety_object(get_relation_constraint_oid(rel->rd_rel->oid, check->ccname, true), ConstraintRelationId, context->max_hazard));
+ }
+
+ context->func_oids = list_concat(context->func_oids, temp_objects);
+ list_free(temp_objects);
+ }
+ }
+
+ return false;
+}
+
/*
* is_parallel_allowed_for_modify
*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4d..18eabcd 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_max_parallel_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_max_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_max_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index de96e96..8c0a705 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,21 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+
+List *
+GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4..2f8c528 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_max_parallel_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_max_parallel_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b5656..7ceb750 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a..28ca7d8 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.7.2.windows.1
v8-0004-regression-test-and-doc-updates.patchapplication/octet-stream; name=v8-0004-regression-test-and-doc-updates.patchDownload
From a5cb6c5e14d93d167c1c30dea399cd197868f706 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 2 Jun 2021 17:30:12 +0800
Subject: [PATCH] regression-test-and-doc-updates
---
contrib/test_decoding/expected/ddl.out | 4 +
doc/src/sgml/func.sgml | 61 ++
doc/src/sgml/ref/alter_foreign_table.sgml | 13 +
doc/src/sgml/ref/alter_table.sgml | 12 +
doc/src/sgml/ref/create_foreign_table.sgml | 37 ++
doc/src/sgml/ref/create_table.sgml | 38 ++
doc/src/sgml/ref/create_table_as.sgml | 23 +
src/test/regress/expected/alter_table.out | 2 +
src/test/regress/expected/compression_1.out | 9 +
src/test/regress/expected/copy2.out | 1 +
src/test/regress/expected/create_table.out | 14 +
.../regress/expected/create_table_like.out | 8 +
src/test/regress/expected/domain.out | 2 +
src/test/regress/expected/foreign_data.out | 42 ++
src/test/regress/expected/identity.out | 1 +
src/test/regress/expected/inherit.out | 13 +
src/test/regress/expected/insert.out | 12 +
src/test/regress/expected/insert_parallel.out | 580 ++++++++++++++++++
src/test/regress/expected/psql.out | 58 +-
src/test/regress/expected/publication.out | 4 +
.../regress/expected/replica_identity.out | 1 +
src/test/regress/expected/rowsecurity.out | 1 +
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats_ext.out | 1 +
src/test/regress/expected/update.out | 1 +
src/test/regress/output/tablespace.source | 2 +
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 346 +++++++++++
28 files changed, 1263 insertions(+), 27 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 4ff0044c78..1d7eebb897 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -446,6 +446,7 @@ WITH (user_catalog_table = true)
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -460,6 +461,7 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
INSERT INTO replication_metadata(relation, options)
VALUES ('bar', ARRAY['a', 'b']);
@@ -473,6 +475,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -492,6 +495,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
rewritemeornot | integer | | | | plain | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=false
INSERT INTO replication_metadata(relation, options)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 08b07f561e..264db45b03 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23909,6 +23909,67 @@ SELECT collation for ('foo' COLLATE "de_DE");
Undefined objects are identified with <literal>NULL</literal> values.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_parallel_safety</primary>
+ </indexterm>
+ <function>pg_get_parallel_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+ <returnvalue>record</returnvalue>
+ ( <parameter>objid</parameter> <type>oid</type>,
+ <parameter>classid</parameter> <type>oid</type>,
+ <parameter>proparallel</parameter> <type>char</type> )
+ </para>
+ <para>
+ Returns a row containing enough information to uniquely identify the
+ parallel unsafe/restricted table-related objects from which the
+ table's parallel DML safety is determined. The user can use this
+ information during development in order to accurately declare a
+ table's parallel DML safety, or to identify any problematic objects
+ if parallel DML fails or behaves unexpectedly. Note that when the
+ use of an object-related parallel unsafe/restricted function is
+ detected, both the function OID and the object OID are returned.
+ <parameter>classid</parameter> is the OID of the system catalog
+ containing the object;
+ <parameter>objid</parameter> is the OID of the object itself.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_max_parallel_hazard</primary>
+ </indexterm>
+ <function>pg_get_max_parallel_hazard</function> ( <type>regclass</type> )
+ <returnvalue>char</returnvalue>
+ </para>
+ <para>
+ Returns the worst parallel DML safety hazard that can be found in the
+ given relation:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>s</literal> safe
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>r</literal> restricted
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>u</literal> unsafe
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ Users can use this function to do a quick check without caring about
+ specific parallel-related objects.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 7ca03f3ac9..c1652e8312 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -29,6 +29,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -299,6 +301,17 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See the similar form of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>
+ for more details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 939d3fe273..194d7a04a7 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -37,6 +37,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -1011,6 +1013,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See <link linkend="sql-createtable"><command>CREATE TABLE</command></link> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477efe58..bb5f582d2c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -290,6 +292,41 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table (e.g., functions in triggers/index expression/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_parallel_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_max_parallel_hazard()</function></link> for more information.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">server_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index c6d0a35e50..8e80b09bb3 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
@@ -1336,6 +1339,41 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-paralleldmlsafety">
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table
+ (e.g., functions in triggers/index expressions/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_parallel_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_max_parallel_hazard()</function></link> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab56c..71c932b048 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
AS <replaceable>query</replaceable>
[ WITH [ NO ] DATA ]
</synopsis>
@@ -223,6 +224,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>query</replaceable></term>
<listitem>
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f81bdf513b..e800a218b5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2203,6 +2203,7 @@ alter table test_storage alter column a set storage external;
b | integer | | | 0 | plain | |
Indexes:
"test_storage_idx" btree (b, a)
+Parallel DML: unsafe
\d+ test_storage_idx
Index "public.test_storage_idx"
@@ -4190,6 +4191,7 @@ ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
a | integer | | | | plain | |
Partition key: RANGE (a)
Number of partitions: 0
+Parallel DML: unsafe
-- constraint should be created
\d part_rp
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index aac96037fc..ca76a1d605 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -12,6 +12,7 @@ INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
ERROR: unsupported LZ4 compression method
@@ -51,6 +52,7 @@ SELECT * INTO cmmove1 FROM cmdata;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | text | | | | extended | | |
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmmove1;
pg_column_compression
@@ -138,6 +140,7 @@ CREATE TABLE cmdata2 (f1 int);
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
\d+ cmdata2
@@ -145,6 +148,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
\d+ cmdata2
@@ -152,6 +156,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
--changing column storage should not impact the compression method
--but the data should not be compressed
@@ -162,6 +167,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | pglz | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
\d+ cmdata2
@@ -169,6 +175,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | pglz | |
+Parallel DML: unsafe
INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
SELECT pg_column_compression(f1) FROM cmdata2;
@@ -249,6 +256,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmdata;
pg_column_compression
@@ -263,6 +271,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | | |
+Parallel DML: unsafe
-- test alter compression method for materialized views
ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c64f0719e7..df20bcb97b 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -517,6 +517,7 @@ alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
f1 | integer | | | | plain | |
Check constraints:
"check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+Parallel DML: unsafe
copy check_con_tbl from stdin;
NOTICE: input = {"f1":1}
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index ad89dd05c1..7a3a4bea25 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,6 +505,7 @@ Number of partitions: 0
b | text | | | | extended | |
Partition key: RANGE (((a + 1)), substr(b, 1, 5))
Number of partitions: 0
+Parallel DML: unsafe
INSERT INTO partitioned2 VALUES (1, 'hello');
ERROR: no partition of relation "partitioned2" found for row
@@ -518,6 +519,7 @@ CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO
b | text | | | | extended | |
Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+Parallel DML: unsafe
DROP TABLE partitioned, partitioned2;
-- check reference to partitioned table's rowtype in partition descriptor
@@ -559,6 +561,7 @@ select * from partitioned where partitioned = '(1,2)'::partitioned;
b | integer | | | | plain | |
Partition of: partitioned FOR VALUES IN ('(1,2)')
Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+Parallel DML: unsafe
drop table partitioned;
-- check that dependencies of partition columns are handled correctly
@@ -618,6 +621,7 @@ Partitions: part_null FOR VALUES IN (NULL),
part_p1 FOR VALUES IN (1),
part_p2 FOR VALUES IN (2),
part_p3 FOR VALUES IN (3)
+Parallel DML: unsafe
-- forbidden expressions for partition bound with list partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
@@ -1058,6 +1062,7 @@ drop table test_part_coll_posix;
b | integer | | not null | 1 | plain | |
Partition of: parted FOR VALUES IN ('b')
Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Parallel DML: unsafe
-- Both partition bound and partition key in describe output
\d+ part_c
@@ -1070,6 +1075,7 @@ Partition of: parted FOR VALUES IN ('c')
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
Partition key: RANGE (b)
Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+Parallel DML: unsafe
-- a level-2 partition's constraint will include the parent's expressions
\d+ part_c_1_10
@@ -1080,6 +1086,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
b | integer | | not null | 0 | plain | |
Partition of: part_c FOR VALUES FROM (1) TO (10)
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Parallel DML: unsafe
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
@@ -1114,6 +1121,7 @@ CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MI
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+Parallel DML: unsafe
DROP TABLE unbounded_range_part;
CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
@@ -1126,6 +1134,7 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALU
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Parallel DML: unsafe
CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
\d+ range_parted4_2
@@ -1137,6 +1146,7 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Parallel DML: unsafe
CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
\d+ range_parted4_3
@@ -1148,6 +1158,7 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Parallel DML: unsafe
DROP TABLE range_parted4;
-- user-defined operator class in partition key
@@ -1184,6 +1195,7 @@ SELECT obj_description('parted_col_comment'::regclass);
b | text | | | | extended | |
Partition key: LIST (a)
Number of partitions: 0
+Parallel DML: unsafe
DROP TABLE parted_col_comment;
-- list partitioning on array type column
@@ -1196,6 +1208,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
a | integer[] | | | | extended | |
Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+Parallel DML: unsafe
DROP TABLE arrlp;
-- partition on boolean column
@@ -1210,6 +1223,7 @@ create table boolspart_f partition of boolspart for values in (false);
Partition key: LIST (a)
Partitions: boolspart_f FOR VALUES IN (false),
boolspart_t FOR VALUES IN (true)
+Parallel DML: unsafe
drop table boolspart;
-- partitions mixing temporary and permanent relations
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 4dc5e6aa5f..af8de78bdd 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -333,6 +333,7 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
a | text | | not null | | main | |
b | text | | | | extended | |
c | text | | | | external | |
+Parallel DML: unsafe
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@@ -342,6 +343,7 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
a | text | | not null | | extended | | A
b | text | | | | extended | | B
c | text | | | | extended | | C
+Parallel DML: unsafe
CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -356,6 +358,7 @@ NOTICE: merging constraint "ctlt1_a_check" with inherited definition
Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
description
@@ -378,6 +381,7 @@ Check constraints:
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1,
ctlt3
+Parallel DML: unsafe
CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -395,6 +399,7 @@ Check constraints:
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
description
@@ -418,6 +423,7 @@ Check constraints:
Statistics objects:
"public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
"public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
+Parallel DML: unsafe
SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
relname | objsubid | description
@@ -458,6 +464,7 @@ Check constraints:
Statistics objects:
"public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
"public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
+Parallel DML: unsafe
DROP TABLE public.pg_attrdef;
-- Check that LIKE isn't confused when new table masks the old, either
@@ -480,6 +487,7 @@ Check constraints:
Statistics objects:
"ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
"ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
+Parallel DML: unsafe
ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..2419d96a33 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -276,6 +276,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
WHERE (dcomptable.d1).i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
@@ -413,6 +414,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
WHERE dcomptable.d1[1].i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 5385f98a0f..4f50410f39 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -731,6 +731,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
\det+
List of foreign tables
@@ -852,6 +853,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- can't change the column type if it's used elsewhere
CREATE TABLE use_ft1_column_type (x ft1);
@@ -1390,6 +1392,7 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1401,6 +1404,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2;
\d+ fd_pt1
@@ -1410,6 +1414,7 @@ DROP FOREIGN TABLE ft2;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Parallel DML: unsafe
CREATE FOREIGN TABLE ft2 (
c1 integer NOT NULL,
@@ -1425,6 +1430,7 @@ CREATE FOREIGN TABLE ft2 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
\d+ fd_pt1
@@ -1435,6 +1441,7 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1446,6 +1453,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
CREATE TABLE ct3() INHERITS(ft2);
CREATE FOREIGN TABLE ft3 (
@@ -1469,6 +1477,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1478,6 +1487,7 @@ Child tables: ct3,
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1488,6 +1498,7 @@ Inherits: ft2
c3 | date | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- add attributes recursively
ALTER TABLE fd_pt1 ADD COLUMN c4 integer;
@@ -1508,6 +1519,7 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1526,6 +1538,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1540,6 +1553,7 @@ Child tables: ct3,
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1555,6 +1569,7 @@ Inherits: ft2
c8 | integer | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- alter attributes recursively
ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0;
@@ -1582,6 +1597,7 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
c7 | integer | | | | plain | |
c8 | text | | | | external | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1600,6 +1616,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- drop attributes recursively
ALTER TABLE fd_pt1 DROP COLUMN c4;
@@ -1615,6 +1632,7 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1628,6 +1646,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- add constraints recursively
ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT;
@@ -1655,6 +1674,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1670,6 +1690,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2; -- ERROR
ERROR: cannot drop foreign table ft2 because other objects depend on it
@@ -1702,6 +1723,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1715,6 +1737,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- drop constraints recursively
ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE;
@@ -1732,6 +1755,7 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1746,6 +1770,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- VALIDATE CONSTRAINT need do nothing on foreign tables
ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
@@ -1759,6 +1784,7 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1773,6 +1799,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- changes name of an attribute recursively
ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1;
@@ -1790,6 +1817,7 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1804,6 +1832,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR
@@ -1853,6 +1882,7 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1865,6 +1895,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- partition cannot have additional columns
DROP FOREIGN TABLE fd_pt2_1;
@@ -1884,6 +1915,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c4 | character(1) | | | | | extended | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
@@ -1898,6 +1930,7 @@ DROP FOREIGN TABLE fd_pt2_1;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
CREATE FOREIGN TABLE fd_pt2_1 (
c1 integer NOT NULL,
@@ -1913,6 +1946,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- no attach partition validation occurs for foreign tables
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
@@ -1925,6 +1959,7 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1937,6 +1972,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot add column to a partition
ALTER TABLE fd_pt2_1 ADD c4 char;
@@ -1953,6 +1989,7 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1967,6 +2004,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot drop inherited NOT NULL constraint from a partition
ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
@@ -1983,6 +2021,7 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1995,6 +2034,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: column "c2" in child table must be marked NOT NULL
@@ -2013,6 +2053,7 @@ Partition key: LIST (c1)
Check constraints:
"fd_pt2chk1" CHECK (c1 > 0)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -2025,6 +2066,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: child table is missing constraint "fd_pt2chk1"
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7..da24c16d09 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -506,6 +506,7 @@ TABLE itest8;
f3 | integer | | not null | generated by default as identity | plain | |
f4 | bigint | | not null | generated always as identity | plain | |
f5 | bigint | | | | plain | |
+Parallel DML: unsafe
\d itest8_f2_seq
Sequence "public.itest8_f2_seq"
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 06f44287bc..33a216ea4d 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1059,6 +1059,7 @@ ALTER TABLE inhts RENAME d TO dd;
dd | integer | | | | plain | |
Inherits: inht1,
inhs1
+Parallel DML: unsafe
DROP TABLE inhts;
-- Test for renaming in diamond inheritance
@@ -1079,6 +1080,7 @@ ALTER TABLE inht1 RENAME aa TO aaa;
z | integer | | | | plain | |
Inherits: inht2,
inht3
+Parallel DML: unsafe
CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
NOTICE: merging multiple inherited definitions of column "b"
@@ -1096,6 +1098,7 @@ ERROR: cannot rename inherited column "b"
d | integer | | | | plain | |
Inherits: inht2,
inhs1
+Parallel DML: unsafe
WITH RECURSIVE r AS (
SELECT 'inht1'::regclass AS inhrelid
@@ -1142,6 +1145,7 @@ CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
Indexes:
"test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
Child tables: test_constraints_inh
+Parallel DML: unsafe
ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
\d+ test_constraints
@@ -1152,6 +1156,7 @@ ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Child tables: test_constraints_inh
+Parallel DML: unsafe
\d+ test_constraints_inh
Table "public.test_constraints_inh"
@@ -1161,6 +1166,7 @@ Child tables: test_constraints_inh
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Inherits: test_constraints
+Parallel DML: unsafe
DROP TABLE test_constraints_inh;
DROP TABLE test_constraints;
@@ -1177,6 +1183,7 @@ CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
Indexes:
"test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
\d+ test_ex_constraints
@@ -1185,6 +1192,7 @@ ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
\d+ test_ex_constraints_inh
Table "public.test_ex_constraints_inh"
@@ -1192,6 +1200,7 @@ Child tables: test_ex_constraints_inh
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Inherits: test_ex_constraints
+Parallel DML: unsafe
DROP TABLE test_ex_constraints_inh;
DROP TABLE test_ex_constraints;
@@ -1208,6 +1217,7 @@ Indexes:
"test_primary_constraints_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Parallel DML: unsafe
\d+ test_foreign_constraints
Table "public.test_foreign_constraints"
@@ -1217,6 +1227,7 @@ Referenced by:
Foreign-key constraints:
"test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
\d+ test_foreign_constraints
@@ -1225,6 +1236,7 @@ ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
\d+ test_foreign_constraints_inh
Table "public.test_foreign_constraints_inh"
@@ -1232,6 +1244,7 @@ Child tables: test_foreign_constraints_inh
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Inherits: test_foreign_constraints
+Parallel DML: unsafe
DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..cb8cd958aa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -177,6 +177,7 @@ Rules:
irule3 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
new.f2
+Parallel DML: unsafe
drop table inserttest2;
drop table inserttest;
@@ -482,6 +483,7 @@ Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
part_null FOR VALUES IN (NULL),
part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
part_default DEFAULT, PARTITIONED
+Parallel DML: unsafe
-- cleanup
drop table range_parted, list_parted;
@@ -497,6 +499,7 @@ create table part_default partition of list_parted default;
a | integer | | | | plain | |
Partition of: list_parted DEFAULT
No partition constraint
+Parallel DML: unsafe
insert into part_default values (null);
insert into part_default values (1);
@@ -888,6 +891,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Parallel DML: unsafe
\d+ mcrparted1_lt_b
Table "public.mcrparted1_lt_b"
@@ -897,6 +901,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+Parallel DML: unsafe
\d+ mcrparted2_b
Table "public.mcrparted2_b"
@@ -906,6 +911,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+Parallel DML: unsafe
\d+ mcrparted3_c_to_common
Table "public.mcrparted3_c_to_common"
@@ -915,6 +921,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+Parallel DML: unsafe
\d+ mcrparted4_common_lt_0
Table "public.mcrparted4_common_lt_0"
@@ -924,6 +931,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+Parallel DML: unsafe
\d+ mcrparted5_common_0_to_10
Table "public.mcrparted5_common_0_to_10"
@@ -933,6 +941,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+Parallel DML: unsafe
\d+ mcrparted6_common_ge_10
Table "public.mcrparted6_common_ge_10"
@@ -942,6 +951,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+Parallel DML: unsafe
\d+ mcrparted7_gt_common_lt_d
Table "public.mcrparted7_gt_common_lt_d"
@@ -951,6 +961,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+Parallel DML: unsafe
\d+ mcrparted8_ge_d
Table "public.mcrparted8_ge_d"
@@ -960,6 +971,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+Parallel DML: unsafe
insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
('comm', -10), ('common', -10), ('common', 0), ('common', 10),
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..ca486c63e6
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,580 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+--
+-- END: setup some tables and data needed by the tests.
+--
+-- 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
+);
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_max_parallel_hazard('para_insert_f1');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names2');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_max_parallel_hazard('names4');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_max_parallel_hazard('temp_names');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('table_check_b');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into table_check_b select * from names;
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+-- insert into names_with_unsafe_trigger select * from names;
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('dom_table_u');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1901673622 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2818,6 +2818,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2825,6 +2826,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\set HIDE_TABLEAM off
\d+ tbl_heap_psql
@@ -2834,6 +2836,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap_psql
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2842,50 +2845,51 @@ Access method: heap_psql
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap
+Parallel DML: unsafe
-- AM is displayed for tables, indexes and materialized views.
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | | unsafe | 0 bytes |
(4 rows)
\dt+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+---------------+-------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(2 rows)
\dm+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(1 row)
-- But not for views and sequences.
\dv+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+----------------+------+----------------------+-------------+---------+-------------
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+----------------+------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(1 row)
\set HIDE_TABLEAM on
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(4 rows)
RESET ROLE;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..314ec05dc1 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -83,6 +83,7 @@ Indexes:
"testpub_tbl2_pkey" PRIMARY KEY, btree (id)
Publications:
"testpub_foralltables"
+Parallel DML: unsafe
\dRp+ testpub_foralltables
Publication testpub_foralltables
@@ -196,6 +197,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\d+ testpub_tbl1
Table "public.testpub_tbl1"
@@ -209,6 +211,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\dRp+ testpub_default
Publication testpub_default
@@ -234,6 +237,7 @@ Indexes:
Publications:
"testpib_ins_trunct"
"testpub_fortbl"
+Parallel DML: unsafe
-- permissions
SET ROLE regress_publication_user2;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..0f8718f2a4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -171,6 +171,7 @@ Indexes:
"test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
"test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
Replica Identity: FULL
+Parallel DML: unsafe
ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 367ecace47..369bea2e65 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -958,6 +958,7 @@ Policies:
Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
part_document_nonfiction FOR VALUES FROM (99) TO (100),
part_document_satire FOR VALUES FROM (55) TO (56)
+Parallel DML: unsafe
SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..cad4be0a8b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3155,6 +3155,7 @@ Rules:
r3 AS
ON DELETE TO rules_src DO
NOTIFY rules_src_deletion
+Parallel DML: unsafe
--
-- Ensure an aliased target relation for insert is correctly deparsed.
@@ -3183,6 +3184,7 @@ Rules:
r5 AS
ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text
WHERE trgt.f1 = new.f1
+Parallel DML: unsafe
--
-- Also check multiassignment deparsing.
@@ -3206,6 +3208,7 @@ Rules:
WHERE trgt.f1 = new.f1
RETURNING new.f1,
new.f2
+Parallel DML: unsafe
drop table rule_t1, rule_dest;
--
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c214d8dfc..c94eb3293e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -145,6 +145,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
b | integer | | | | plain | |
Statistics objects:
"public"."ab1_a_b_stats" ON a, b FROM ab1
+Parallel DML: unsafe
-- partial analyze doesn't build stats either
ANALYZE ab1 (a);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index c809f88f54..3fcd8e10f3 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -753,6 +753,7 @@ create table part_def partition of range_parted default;
e | character varying | | | | extended | |
Partition of: range_parted DEFAULT
Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
+Parallel DML: unsafe
insert into range_parted values ('c', 9);
-- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..11a750b58d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -339,6 +339,7 @@ Indexes:
"part_a_idx" btree (a), tablespace "regress_tblspace"
Partitions: testschema.part1 FOR VALUES IN (1),
testschema.part2 FOR VALUES IN (2)
+Parallel DML: unsafe
\d testschema.part1
Table "testschema.part1"
@@ -358,6 +359,7 @@ Partition of: testschema.part FOR VALUES IN (1)
Partition constraint: ((a IS NOT NULL) AND (a = 1))
Indexes:
"part1_a_idx" btree (a), tablespace "regress_tblspace"
+Parallel DML: unsafe
\d testschema.part_a_idx
Partitioned index "testschema.part_a_idx"
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..0686cb6646
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,346 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+-- 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
+);
+
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+select pg_get_max_parallel_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+select pg_get_max_parallel_hazard('names2');
+alter table names2 parallel dml safe;
+-- insert into names2 select * from names returning *;
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+select pg_get_max_parallel_hazard('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+select pg_get_max_parallel_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+-- (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);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+select pg_get_max_parallel_hazard('table_check_b');
+-- insert into table_check_b select * from names;
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+-- (should not create a parallel plan; triggers should fire)
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test partition with parallel-unsafe trigger
+-- (should not create a parallel plan)
+--
+
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+-- insert into names_with_unsafe_trigger select * from names;
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+select pg_get_max_parallel_hazard('dom_table_u');
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
Hi,
When testing the patch, I found some issues in the 0003,0004 patch.
Attaching new version patchset which fix these issues.
0003
* don't check parallel safety of partition's default column expression.
* rename some function/variable names to be consistent with existing code.
* remove some unused function parameters.
* fix a max_hazard overwrite issue.
* add some code comments and adjust some code forms.
0004
* Remove some unrelated comments in the regression test.
* add the 'begin;', 'rollback;' in insert_parallel.sql.
Best regards,
houzj
Attachments:
v9-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v9-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 57e72e841f87ba4380f1abef88e76ed4609e4348 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Wed, 2 Jun 2021 11:14:26 +0800
Subject: [PATCH] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 ++++++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 65 +++++++++++-----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 69 +++++++++++++++--
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../modules/test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 277 insertions(+), 39 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..88fcd57 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index afa830d..7b1152d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -302,6 +302,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -404,7 +405,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -961,6 +963,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1154,6 +1157,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1301,6 +1305,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 50b7a16..ce2ae5a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index bf81f6c..d0a9fea 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e..2151121 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce8820..45aacc8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9..6f25c23 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac..5d14b1e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -925,6 +928,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -943,6 +968,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4184,6 +4210,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4717,6 +4744,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5119,6 +5151,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18614,3 +18649,55 @@ GetAttributeCompression(Oid atttypid, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c..8baebe0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642db..2d77a88 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a8..6bf8787 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3531,6 +3531,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d09..aaed9d1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1284,6 +1285,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14..16c66a8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2703,6 +2704,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea0..09f5f0d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ee90e3..f099795 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -609,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2683,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3276,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3290,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3310,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3331,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3352,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3374,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3396,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4081,6 +4097,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4228,7 +4248,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4237,6 +4257,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5016,7 +5037,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5028,15 +5049,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5048,15 +5070,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5069,15 +5092,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5090,10 +5114,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15563,6 +15588,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16103,6 +16129,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615..ef6200d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1870,6 +1870,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3356,7 +3357,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3506,6 +3508,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7..abca220 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6227,6 +6227,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relproparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6331,7 +6332,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6423,7 +6424,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6476,7 +6477,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6529,7 +6530,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6582,7 +6583,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6633,7 +6634,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6681,7 +6682,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6729,7 +6730,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6776,7 +6777,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6845,6 +6846,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relproparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6900,6 +6902,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relproparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16423,6 +16426,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a..e083593 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 195f8d8..f6bab2f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1656,6 +1656,7 @@ describeOneTableDetails(const char *schemaname,
char *reloftype;
char relpersistence;
char relreplident;
+ char relparalleldml;
char *relam;
} tableinfo;
bool show_column_details = false;
@@ -1669,7 +1670,25 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
- if (pset.sversion >= 120000)
+ if (pset.sversion >= 140000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+ "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+ "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+ "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+ "c.relpersistence, c.relreplident, am.amname, c.relparalleldml\n"
+ "FROM pg_catalog.pg_class c\n "
+ "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+ "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+ "WHERE c.oid = '%s';",
+ (verbose ?
+ "pg_catalog.array_to_string(c.reloptions || "
+ "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+ : "''"),
+ oid);
+ }
+ else if (pset.sversion >= 120000)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1872,8 @@ describeOneTableDetails(const char *schemaname,
(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
else
tableinfo.relam = NULL;
+ tableinfo.relparalleldml = (pset.sversion >= 140000) ?
+ *(PQgetvalue(res, 0, 15)) : 0;
PQclear(res);
res = NULL;
@@ -3630,6 +3651,20 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
printTableAddFooter(&cont, buf.data);
}
+
+ if (verbose &&
+ (tableinfo.relkind == RELKIND_RELATION ||
+ tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+ tableinfo.relkind == RELKIND_FOREIGN_TABLE) &&
+ tableinfo.relparalleldml != 0)
+ {
+ printfPQExpBuffer(&buf, _("Parallel DML: %s"),
+ tableinfo.relparalleldml == 'u' ? "unsafe" :
+ tableinfo.relparalleldml == 'r' ? "restricted" :
+ tableinfo.relparalleldml == 's' ? "safe" :
+ "???");
+ printTableAddFooter(&cont, buf.data);
+ }
}
/* reloptions, if verbose */
@@ -4005,7 +4040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
PGresult *res;
printQueryOpt myopt = pset.popt;
int cols_so_far;
- bool translate_columns[] = {false, false, true, false, false, false, false, false, false};
+ bool translate_columns[] = {false, false, true, false, false, false, false, false, false, false};
/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
@@ -4073,22 +4108,42 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("unlogged"),
gettext_noop("Persistence"));
translate_columns[cols_so_far] = true;
+ cols_so_far++;
}
/*
- * We don't bother to count cols_so_far below here, as there's no need
- * to; this might change with future additions to the output columns.
- */
-
- /*
* Access methods exist for tables, materialized views and indexes.
* This has been introduced in PostgreSQL 12 for tables.
*/
if (pset.sversion >= 120000 && !pset.hide_tableam &&
(showTables || showMatViews || showIndexes))
+ {
appendPQExpBuffer(&buf,
",\n am.amname as \"%s\"",
gettext_noop("Access method"));
+ cols_so_far++;
+ }
+
+ /*
+ * Show whether the data in the relation is unsafe('u'),
+ * restricted('r'), or safe('s') can be modified in parallel mode.
+ * This has been introduced in PostgreSQL 15 for tables.
+ */
+ if (pset.sversion >= 140000)
+ {
+ appendPQExpBuffer(&buf,
+ ",\n CASE c.relparalleldml WHEN 'u' THEN '%s' WHEN 'r' THEN '%s' WHEN 's' THEN '%s' END as \"%s\"",
+ gettext_noop("unsafe"),
+ gettext_noop("restricted"),
+ gettext_noop("safe"),
+ gettext_noop("Parallel DML"));
+ translate_columns[cols_so_far] = true;
+ }
+
+ /*
+ * We don't bother to count cols_so_far below here, as there's no need
+ * to; this might change with future additions to the output columns.
+ */
/*
* As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..b599759 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729..af280b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342..dcdf6db 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2168,6 +2169,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d..6b532b0 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf..05222fa 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855..5ea225a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5..e1f5678 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.7.2.windows.1
v9-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v9-0002-parallel-SELECT-for-INSERT.patchDownload
From 49f57ad7508e2ee33fade8a1a7358b7c4ec09eaa Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:32:54 +0800
Subject: [PATCH 2/3] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++++
src/backend/executor/execMain.c | 3 ++
src/backend/optimizer/plan/planner.c | 21 ++++-----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4414459..2d68e46 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1015,6 +1015,32 @@ IsInParallelMode(void)
}
/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
+/*
* CommandCounterIncrement
*/
void
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4ba..ea685f0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4e..7736813 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 517712a..7c58c88 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f686..fd3f86b 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887..32b5656 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.7.2.windows.1
v9-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v9-0003-get-parallel-safety-functions.patchDownload
From 63452faf2410223c99bcf9926896d7688091906a Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 8 Jun 2021 16:45:11 +0800
Subject: [PATCH] get-parallel-safety-functions
Provide a utility function "pg_get_parallel_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_max_parallel_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 633 ++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 ++++
src/backend/utils/cache/typcache.c | 15 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 771 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7c58c8809b..c569d33739 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all; /* whether collect all the unsafe/restricted objects */
+ List *objects; /* parallel unsafe/restricted objects */
+ PartitionDirectory partition_directory; /* partition descriptors */
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,23 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context,
+ bool recurse_partition);
+static bool target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context);
+static bool target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static List *target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +181,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +655,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +710,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +744,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +761,66 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+static safety_object *
+make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+/* check_functions_in_node callback */
+static bool
+parallel_hazard_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object = make_safety_object(func_id,
+ ProcedureRelationId,
+ proparallel);
+ cont->objects = lappend(cont->objects, object);
+ }
+
+ return false;
+}
+
+static bool
+parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_hazard_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_max_parallel_hazard(domain->resulttype, context) &&
+ !context->check_all)
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_hazard_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -881,6 +976,542 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * target_rel_max_parallel_hazard
+ * Detect all parallel unsafe/restricted table-related objects.
+ */
+List*
+target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.objects = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_all_parallel_hazard_recurse(targetRel, &context, false);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.objects;
+}
+
+
+static bool
+target_rel_all_parallel_hazard_recurse(Relation rel,
+ max_parallel_hazard_context *context,
+ bool recurse_partition)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ {
+ safety_object *object = make_safety_object(rel->rd_rel->oid,
+ RelationRelationId,
+ PROPARALLEL_RESTRICTED);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_max_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef && !recurse_partition)
+ {
+ Node *defaultexpr;
+
+ defaultexpr = build_column_default(rel, attnum + 1);
+ if (parallel_hazard_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_max_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_max_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified relation's
+ * trigger data.
+ */
+static bool
+target_rel_trigger_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object,
+ *parent_object;
+
+ object = make_safety_object(tgfoid, ProcedureRelationId,
+ proparallel);
+ parent_object = make_safety_object(tgoid, TriggerRelationId,
+ proparallel);
+
+ context->objects = lappend(context->objects, object);
+ context->objects = lappend(context->objects, parent_object);
+ }
+ }
+
+ return false;
+}
+
+static bool
+index_expr_max_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ int i;
+ Form_pg_index indexStruct;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ /* Check index expression */
+ if (ii_Expressions != NIL)
+ {
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_hazard_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+ }
+
+ /* Check index predicate */
+ if (ii_Predicate != NIL)
+ {
+ if (parallel_hazard_walker((Node *) ii_Predicate, context))
+ return true;
+ }
+
+ /*
+ * Check parallel-safety of any index AM support functions.
+ */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+ if (nsupport > 0)
+ {
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+ Oid funcOid = index_rel->rd_support[i];
+
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object;
+
+ object = make_safety_object(funcOid, ProcedureRelationId,
+ proparallel);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_index_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any existing index
+ * expressions or index predicate of a specified relation.
+ */
+static bool
+target_rel_index_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ char temp_hazard;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_max_parallel_hazard(index_rel,
+ ii_Expressions,
+ ii_Predicate,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+
+ object = make_safety_object(index_oid, IndexRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for the specified DOMAIN
+ * type. Only any CHECK expressions are examined for parallel-safety.
+ */
+static bool
+target_rel_domain_max_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+ char temp_hazard;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) r->check_expr, context) &&
+ !context->check_all)
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+ Oid constr_oid = get_domain_constraint_oid(typid,
+ r->name,
+ false);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any partitions of a
+ * of a specified relation.
+ */
+static bool
+target_rel_partitions_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs,
+ *qual;
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_hazard_walker((Node *) qual, context))
+ return true;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object;
+
+ object = make_safety_object(funcOid, ProcedureRelationId,
+ proparallel);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_hazard_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_all_parallel_hazard_recurse(part_rel,
+ context,
+ true);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ break;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_max_parallel_hazard
+ *
+ * Finds all the PARALLEL UNSAFE/RESTRICTED objects for any CHECK expressions
+ * or CHECK constraints related to the specified relation.
+ */
+static List*
+target_rel_chk_constr_max_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ TupleDesc tupdesc;
+ List *temp_objects;
+ char temp_hazard;
+
+ tupdesc = RelationGetDescr(rel);
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ if (tupdesc->constr != NULL && tupdesc->constr->num_check > 0)
+ {
+ int i;
+ ConstrCheck *check = tupdesc->constr->check;
+
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ parallel_hazard_walker((Node *) check_expr, context);
+
+ /* Add the constraint itself to the objects list */
+ if (context->objects != NIL)
+ {
+ Oid constr_oid;
+ safety_object *object;
+
+ constr_oid = get_relation_constraint_oid(rel->rd_rel->oid,
+
+ check->ccname,
+ true);
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+
+
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+ }
+
+ return false;
+}
+
/*
* is_parallel_allowed_for_modify
*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4dfd7..18eabcd630 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_max_parallel_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_max_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_max_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index de96e96c8f..8c0a70542c 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,21 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+
+List *
+GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4607..2f8c52814a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_max_parallel_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_max_parallel_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b56565e5..7ceb750b50 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_max_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a4b7..28ca7d8a6e 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.18.4
v9-0004-regression-test-and-doc-updates.patchapplication/octet-stream; name=v9-0004-regression-test-and-doc-updates.patchDownload
From 1df6890b2a7986853e87db36f094bfe56098ae1a Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 8 Jun 2021 16:42:34 +0800
Subject: [PATCH] regression-test-and-doc-updates
---
contrib/test_decoding/expected/ddl.out | 4 +
doc/src/sgml/func.sgml | 61 ++
doc/src/sgml/ref/alter_foreign_table.sgml | 13 +
doc/src/sgml/ref/alter_table.sgml | 12 +
doc/src/sgml/ref/create_foreign_table.sgml | 37 ++
doc/src/sgml/ref/create_table.sgml | 38 ++
doc/src/sgml/ref/create_table_as.sgml | 23 +
src/test/regress/expected/alter_table.out | 2 +
src/test/regress/expected/compression_1.out | 9 +
src/test/regress/expected/copy2.out | 1 +
src/test/regress/expected/create_table.out | 14 +
.../regress/expected/create_table_like.out | 8 +
src/test/regress/expected/domain.out | 2 +
src/test/regress/expected/foreign_data.out | 42 ++
src/test/regress/expected/identity.out | 1 +
src/test/regress/expected/inherit.out | 13 +
src/test/regress/expected/insert.out | 12 +
src/test/regress/expected/insert_parallel.out | 575 ++++++++++++++++++
src/test/regress/expected/psql.out | 58 +-
src/test/regress/expected/publication.out | 4 +
.../regress/expected/replica_identity.out | 1 +
src/test/regress/expected/rowsecurity.out | 1 +
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats_ext.out | 1 +
src/test/regress/expected/update.out | 1 +
src/test/regress/output/tablespace.source | 2 +
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 343 +++++++++++
28 files changed, 1255 insertions(+), 27 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 4ff0044c78..1d7eebb897 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -446,6 +446,7 @@ WITH (user_catalog_table = true)
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -460,6 +461,7 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
INSERT INTO replication_metadata(relation, options)
VALUES ('bar', ARRAY['a', 'b']);
@@ -473,6 +475,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -492,6 +495,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
rewritemeornot | integer | | | | plain | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=false
INSERT INTO replication_metadata(relation, options)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 08b07f561e..264db45b03 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23909,6 +23909,67 @@ SELECT collation for ('foo' COLLATE "de_DE");
Undefined objects are identified with <literal>NULL</literal> values.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_parallel_safety</primary>
+ </indexterm>
+ <function>pg_get_parallel_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+ <returnvalue>record</returnvalue>
+ ( <parameter>objid</parameter> <type>oid</type>,
+ <parameter>classid</parameter> <type>oid</type>,
+ <parameter>proparallel</parameter> <type>char</type> )
+ </para>
+ <para>
+ Returns a row containing enough information to uniquely identify the
+ parallel unsafe/restricted table-related objects from which the
+ table's parallel DML safety is determined. The user can use this
+ information during development in order to accurately declare a
+ table's parallel DML safety, or to identify any problematic objects
+ if parallel DML fails or behaves unexpectedly. Note that when the
+ use of an object-related parallel unsafe/restricted function is
+ detected, both the function OID and the object OID are returned.
+ <parameter>classid</parameter> is the OID of the system catalog
+ containing the object;
+ <parameter>objid</parameter> is the OID of the object itself.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_max_parallel_hazard</primary>
+ </indexterm>
+ <function>pg_get_max_parallel_hazard</function> ( <type>regclass</type> )
+ <returnvalue>char</returnvalue>
+ </para>
+ <para>
+ Returns the worst parallel DML safety hazard that can be found in the
+ given relation:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>s</literal> safe
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>r</literal> restricted
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>u</literal> unsafe
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ Users can use this function to do a quick check without caring about
+ specific parallel-related objects.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 7ca03f3ac9..c1652e8312 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -29,6 +29,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -299,6 +301,17 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See the similar form of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>
+ for more details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 939d3fe273..194d7a04a7 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -37,6 +37,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -1011,6 +1013,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See <link linkend="sql-createtable"><command>CREATE TABLE</command></link> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477efe58..bb5f582d2c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -290,6 +292,41 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table (e.g., functions in triggers/index expression/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_parallel_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_max_parallel_hazard()</function></link> for more information.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">server_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index c6d0a35e50..8e80b09bb3 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
@@ -1336,6 +1339,41 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-paralleldmlsafety">
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table
+ (e.g., functions in triggers/index expressions/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_parallel_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_max_parallel_hazard()</function></link> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab56c..71c932b048 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
AS <replaceable>query</replaceable>
[ WITH [ NO ] DATA ]
</synopsis>
@@ -223,6 +224,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>query</replaceable></term>
<listitem>
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f81bdf513b..e800a218b5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2203,6 +2203,7 @@ alter table test_storage alter column a set storage external;
b | integer | | | 0 | plain | |
Indexes:
"test_storage_idx" btree (b, a)
+Parallel DML: unsafe
\d+ test_storage_idx
Index "public.test_storage_idx"
@@ -4190,6 +4191,7 @@ ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
a | integer | | | | plain | |
Partition key: RANGE (a)
Number of partitions: 0
+Parallel DML: unsafe
-- constraint should be created
\d part_rp
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index aac96037fc..ca76a1d605 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -12,6 +12,7 @@ INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
ERROR: unsupported LZ4 compression method
@@ -51,6 +52,7 @@ SELECT * INTO cmmove1 FROM cmdata;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | text | | | | extended | | |
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmmove1;
pg_column_compression
@@ -138,6 +140,7 @@ CREATE TABLE cmdata2 (f1 int);
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
\d+ cmdata2
@@ -145,6 +148,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
\d+ cmdata2
@@ -152,6 +156,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
--changing column storage should not impact the compression method
--but the data should not be compressed
@@ -162,6 +167,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | pglz | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
\d+ cmdata2
@@ -169,6 +175,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | pglz | |
+Parallel DML: unsafe
INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
SELECT pg_column_compression(f1) FROM cmdata2;
@@ -249,6 +256,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmdata;
pg_column_compression
@@ -263,6 +271,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | | |
+Parallel DML: unsafe
-- test alter compression method for materialized views
ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c64f0719e7..df20bcb97b 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -517,6 +517,7 @@ alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
f1 | integer | | | | plain | |
Check constraints:
"check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+Parallel DML: unsafe
copy check_con_tbl from stdin;
NOTICE: input = {"f1":1}
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index ad89dd05c1..7a3a4bea25 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,6 +505,7 @@ Number of partitions: 0
b | text | | | | extended | |
Partition key: RANGE (((a + 1)), substr(b, 1, 5))
Number of partitions: 0
+Parallel DML: unsafe
INSERT INTO partitioned2 VALUES (1, 'hello');
ERROR: no partition of relation "partitioned2" found for row
@@ -518,6 +519,7 @@ CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO
b | text | | | | extended | |
Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+Parallel DML: unsafe
DROP TABLE partitioned, partitioned2;
-- check reference to partitioned table's rowtype in partition descriptor
@@ -559,6 +561,7 @@ select * from partitioned where partitioned = '(1,2)'::partitioned;
b | integer | | | | plain | |
Partition of: partitioned FOR VALUES IN ('(1,2)')
Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+Parallel DML: unsafe
drop table partitioned;
-- check that dependencies of partition columns are handled correctly
@@ -618,6 +621,7 @@ Partitions: part_null FOR VALUES IN (NULL),
part_p1 FOR VALUES IN (1),
part_p2 FOR VALUES IN (2),
part_p3 FOR VALUES IN (3)
+Parallel DML: unsafe
-- forbidden expressions for partition bound with list partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
@@ -1058,6 +1062,7 @@ drop table test_part_coll_posix;
b | integer | | not null | 1 | plain | |
Partition of: parted FOR VALUES IN ('b')
Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Parallel DML: unsafe
-- Both partition bound and partition key in describe output
\d+ part_c
@@ -1070,6 +1075,7 @@ Partition of: parted FOR VALUES IN ('c')
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
Partition key: RANGE (b)
Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+Parallel DML: unsafe
-- a level-2 partition's constraint will include the parent's expressions
\d+ part_c_1_10
@@ -1080,6 +1086,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
b | integer | | not null | 0 | plain | |
Partition of: part_c FOR VALUES FROM (1) TO (10)
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Parallel DML: unsafe
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
@@ -1114,6 +1121,7 @@ CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MI
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+Parallel DML: unsafe
DROP TABLE unbounded_range_part;
CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
@@ -1126,6 +1134,7 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALU
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Parallel DML: unsafe
CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
\d+ range_parted4_2
@@ -1137,6 +1146,7 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Parallel DML: unsafe
CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
\d+ range_parted4_3
@@ -1148,6 +1158,7 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Parallel DML: unsafe
DROP TABLE range_parted4;
-- user-defined operator class in partition key
@@ -1184,6 +1195,7 @@ SELECT obj_description('parted_col_comment'::regclass);
b | text | | | | extended | |
Partition key: LIST (a)
Number of partitions: 0
+Parallel DML: unsafe
DROP TABLE parted_col_comment;
-- list partitioning on array type column
@@ -1196,6 +1208,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
a | integer[] | | | | extended | |
Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+Parallel DML: unsafe
DROP TABLE arrlp;
-- partition on boolean column
@@ -1210,6 +1223,7 @@ create table boolspart_f partition of boolspart for values in (false);
Partition key: LIST (a)
Partitions: boolspart_f FOR VALUES IN (false),
boolspart_t FOR VALUES IN (true)
+Parallel DML: unsafe
drop table boolspart;
-- partitions mixing temporary and permanent relations
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 4dc5e6aa5f..af8de78bdd 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -333,6 +333,7 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
a | text | | not null | | main | |
b | text | | | | extended | |
c | text | | | | external | |
+Parallel DML: unsafe
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@@ -342,6 +343,7 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
a | text | | not null | | extended | | A
b | text | | | | extended | | B
c | text | | | | extended | | C
+Parallel DML: unsafe
CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -356,6 +358,7 @@ NOTICE: merging constraint "ctlt1_a_check" with inherited definition
Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
description
@@ -378,6 +381,7 @@ Check constraints:
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1,
ctlt3
+Parallel DML: unsafe
CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -395,6 +399,7 @@ Check constraints:
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
description
@@ -418,6 +423,7 @@ Check constraints:
Statistics objects:
"public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
"public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
+Parallel DML: unsafe
SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
relname | objsubid | description
@@ -458,6 +464,7 @@ Check constraints:
Statistics objects:
"public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
"public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
+Parallel DML: unsafe
DROP TABLE public.pg_attrdef;
-- Check that LIKE isn't confused when new table masks the old, either
@@ -480,6 +487,7 @@ Check constraints:
Statistics objects:
"ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
"ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
+Parallel DML: unsafe
ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..2419d96a33 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -276,6 +276,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
WHERE (dcomptable.d1).i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
@@ -413,6 +414,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
WHERE dcomptable.d1[1].i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 5385f98a0f..4f50410f39 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -731,6 +731,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
\det+
List of foreign tables
@@ -852,6 +853,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- can't change the column type if it's used elsewhere
CREATE TABLE use_ft1_column_type (x ft1);
@@ -1390,6 +1392,7 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1401,6 +1404,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2;
\d+ fd_pt1
@@ -1410,6 +1414,7 @@ DROP FOREIGN TABLE ft2;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Parallel DML: unsafe
CREATE FOREIGN TABLE ft2 (
c1 integer NOT NULL,
@@ -1425,6 +1430,7 @@ CREATE FOREIGN TABLE ft2 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
\d+ fd_pt1
@@ -1435,6 +1441,7 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1446,6 +1453,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
CREATE TABLE ct3() INHERITS(ft2);
CREATE FOREIGN TABLE ft3 (
@@ -1469,6 +1477,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1478,6 +1487,7 @@ Child tables: ct3,
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1488,6 +1498,7 @@ Inherits: ft2
c3 | date | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- add attributes recursively
ALTER TABLE fd_pt1 ADD COLUMN c4 integer;
@@ -1508,6 +1519,7 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1526,6 +1538,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1540,6 +1553,7 @@ Child tables: ct3,
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1555,6 +1569,7 @@ Inherits: ft2
c8 | integer | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- alter attributes recursively
ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0;
@@ -1582,6 +1597,7 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
c7 | integer | | | | plain | |
c8 | text | | | | external | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1600,6 +1616,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- drop attributes recursively
ALTER TABLE fd_pt1 DROP COLUMN c4;
@@ -1615,6 +1632,7 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1628,6 +1646,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- add constraints recursively
ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT;
@@ -1655,6 +1674,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1670,6 +1690,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2; -- ERROR
ERROR: cannot drop foreign table ft2 because other objects depend on it
@@ -1702,6 +1723,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1715,6 +1737,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- drop constraints recursively
ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE;
@@ -1732,6 +1755,7 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1746,6 +1770,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- VALIDATE CONSTRAINT need do nothing on foreign tables
ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
@@ -1759,6 +1784,7 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1773,6 +1799,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- changes name of an attribute recursively
ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1;
@@ -1790,6 +1817,7 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1804,6 +1832,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR
@@ -1853,6 +1882,7 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1865,6 +1895,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- partition cannot have additional columns
DROP FOREIGN TABLE fd_pt2_1;
@@ -1884,6 +1915,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c4 | character(1) | | | | | extended | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
@@ -1898,6 +1930,7 @@ DROP FOREIGN TABLE fd_pt2_1;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
CREATE FOREIGN TABLE fd_pt2_1 (
c1 integer NOT NULL,
@@ -1913,6 +1946,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- no attach partition validation occurs for foreign tables
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
@@ -1925,6 +1959,7 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1937,6 +1972,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot add column to a partition
ALTER TABLE fd_pt2_1 ADD c4 char;
@@ -1953,6 +1989,7 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1967,6 +2004,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot drop inherited NOT NULL constraint from a partition
ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
@@ -1983,6 +2021,7 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1995,6 +2034,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: column "c2" in child table must be marked NOT NULL
@@ -2013,6 +2053,7 @@ Partition key: LIST (c1)
Check constraints:
"fd_pt2chk1" CHECK (c1 > 0)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -2025,6 +2066,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: child table is missing constraint "fd_pt2chk1"
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7..da24c16d09 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -506,6 +506,7 @@ TABLE itest8;
f3 | integer | | not null | generated by default as identity | plain | |
f4 | bigint | | not null | generated always as identity | plain | |
f5 | bigint | | | | plain | |
+Parallel DML: unsafe
\d itest8_f2_seq
Sequence "public.itest8_f2_seq"
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 06f44287bc..33a216ea4d 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1059,6 +1059,7 @@ ALTER TABLE inhts RENAME d TO dd;
dd | integer | | | | plain | |
Inherits: inht1,
inhs1
+Parallel DML: unsafe
DROP TABLE inhts;
-- Test for renaming in diamond inheritance
@@ -1079,6 +1080,7 @@ ALTER TABLE inht1 RENAME aa TO aaa;
z | integer | | | | plain | |
Inherits: inht2,
inht3
+Parallel DML: unsafe
CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
NOTICE: merging multiple inherited definitions of column "b"
@@ -1096,6 +1098,7 @@ ERROR: cannot rename inherited column "b"
d | integer | | | | plain | |
Inherits: inht2,
inhs1
+Parallel DML: unsafe
WITH RECURSIVE r AS (
SELECT 'inht1'::regclass AS inhrelid
@@ -1142,6 +1145,7 @@ CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
Indexes:
"test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
Child tables: test_constraints_inh
+Parallel DML: unsafe
ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
\d+ test_constraints
@@ -1152,6 +1156,7 @@ ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Child tables: test_constraints_inh
+Parallel DML: unsafe
\d+ test_constraints_inh
Table "public.test_constraints_inh"
@@ -1161,6 +1166,7 @@ Child tables: test_constraints_inh
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Inherits: test_constraints
+Parallel DML: unsafe
DROP TABLE test_constraints_inh;
DROP TABLE test_constraints;
@@ -1177,6 +1183,7 @@ CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
Indexes:
"test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
\d+ test_ex_constraints
@@ -1185,6 +1192,7 @@ ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
\d+ test_ex_constraints_inh
Table "public.test_ex_constraints_inh"
@@ -1192,6 +1200,7 @@ Child tables: test_ex_constraints_inh
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Inherits: test_ex_constraints
+Parallel DML: unsafe
DROP TABLE test_ex_constraints_inh;
DROP TABLE test_ex_constraints;
@@ -1208,6 +1217,7 @@ Indexes:
"test_primary_constraints_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Parallel DML: unsafe
\d+ test_foreign_constraints
Table "public.test_foreign_constraints"
@@ -1217,6 +1227,7 @@ Referenced by:
Foreign-key constraints:
"test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
\d+ test_foreign_constraints
@@ -1225,6 +1236,7 @@ ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
\d+ test_foreign_constraints_inh
Table "public.test_foreign_constraints_inh"
@@ -1232,6 +1244,7 @@ Child tables: test_foreign_constraints_inh
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Inherits: test_foreign_constraints
+Parallel DML: unsafe
DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..cb8cd958aa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -177,6 +177,7 @@ Rules:
irule3 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
new.f2
+Parallel DML: unsafe
drop table inserttest2;
drop table inserttest;
@@ -482,6 +483,7 @@ Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
part_null FOR VALUES IN (NULL),
part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
part_default DEFAULT, PARTITIONED
+Parallel DML: unsafe
-- cleanup
drop table range_parted, list_parted;
@@ -497,6 +499,7 @@ create table part_default partition of list_parted default;
a | integer | | | | plain | |
Partition of: list_parted DEFAULT
No partition constraint
+Parallel DML: unsafe
insert into part_default values (null);
insert into part_default values (1);
@@ -888,6 +891,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Parallel DML: unsafe
\d+ mcrparted1_lt_b
Table "public.mcrparted1_lt_b"
@@ -897,6 +901,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+Parallel DML: unsafe
\d+ mcrparted2_b
Table "public.mcrparted2_b"
@@ -906,6 +911,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+Parallel DML: unsafe
\d+ mcrparted3_c_to_common
Table "public.mcrparted3_c_to_common"
@@ -915,6 +921,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+Parallel DML: unsafe
\d+ mcrparted4_common_lt_0
Table "public.mcrparted4_common_lt_0"
@@ -924,6 +931,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+Parallel DML: unsafe
\d+ mcrparted5_common_0_to_10
Table "public.mcrparted5_common_0_to_10"
@@ -933,6 +941,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+Parallel DML: unsafe
\d+ mcrparted6_common_ge_10
Table "public.mcrparted6_common_ge_10"
@@ -942,6 +951,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+Parallel DML: unsafe
\d+ mcrparted7_gt_common_lt_d
Table "public.mcrparted7_gt_common_lt_d"
@@ -951,6 +961,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+Parallel DML: unsafe
\d+ mcrparted8_ge_d
Table "public.mcrparted8_ge_d"
@@ -960,6 +971,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+Parallel DML: unsafe
insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
('comm', -10), ('common', -10), ('common', 0), ('common', 10),
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..64e6380327
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,575 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+--
+-- END: setup some tables and data needed by the tests.
+--
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_max_parallel_hazard('para_insert_f1');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names2');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+alter table names2 parallel dml safe;
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_max_parallel_hazard('names4');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_max_parallel_hazard('temp_names');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('table_check_b');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('dom_table_u');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+rollback;
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1901673622 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2818,6 +2818,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2825,6 +2826,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\set HIDE_TABLEAM off
\d+ tbl_heap_psql
@@ -2834,6 +2836,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap_psql
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2842,50 +2845,51 @@ Access method: heap_psql
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap
+Parallel DML: unsafe
-- AM is displayed for tables, indexes and materialized views.
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | | unsafe | 0 bytes |
(4 rows)
\dt+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+---------------+-------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(2 rows)
\dm+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(1 row)
-- But not for views and sequences.
\dv+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+----------------+------+----------------------+-------------+---------+-------------
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+----------------+------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(1 row)
\set HIDE_TABLEAM on
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(4 rows)
RESET ROLE;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..314ec05dc1 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -83,6 +83,7 @@ Indexes:
"testpub_tbl2_pkey" PRIMARY KEY, btree (id)
Publications:
"testpub_foralltables"
+Parallel DML: unsafe
\dRp+ testpub_foralltables
Publication testpub_foralltables
@@ -196,6 +197,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\d+ testpub_tbl1
Table "public.testpub_tbl1"
@@ -209,6 +211,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\dRp+ testpub_default
Publication testpub_default
@@ -234,6 +237,7 @@ Indexes:
Publications:
"testpib_ins_trunct"
"testpub_fortbl"
+Parallel DML: unsafe
-- permissions
SET ROLE regress_publication_user2;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..0f8718f2a4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -171,6 +171,7 @@ Indexes:
"test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
"test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
Replica Identity: FULL
+Parallel DML: unsafe
ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 367ecace47..369bea2e65 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -958,6 +958,7 @@ Policies:
Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
part_document_nonfiction FOR VALUES FROM (99) TO (100),
part_document_satire FOR VALUES FROM (55) TO (56)
+Parallel DML: unsafe
SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..cad4be0a8b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3155,6 +3155,7 @@ Rules:
r3 AS
ON DELETE TO rules_src DO
NOTIFY rules_src_deletion
+Parallel DML: unsafe
--
-- Ensure an aliased target relation for insert is correctly deparsed.
@@ -3183,6 +3184,7 @@ Rules:
r5 AS
ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text
WHERE trgt.f1 = new.f1
+Parallel DML: unsafe
--
-- Also check multiassignment deparsing.
@@ -3206,6 +3208,7 @@ Rules:
WHERE trgt.f1 = new.f1
RETURNING new.f1,
new.f2
+Parallel DML: unsafe
drop table rule_t1, rule_dest;
--
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c214d8dfc..c94eb3293e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -145,6 +145,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
b | integer | | | | plain | |
Statistics objects:
"public"."ab1_a_b_stats" ON a, b FROM ab1
+Parallel DML: unsafe
-- partial analyze doesn't build stats either
ANALYZE ab1 (a);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index c809f88f54..3fcd8e10f3 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -753,6 +753,7 @@ create table part_def partition of range_parted default;
e | character varying | | | | extended | |
Partition of: range_parted DEFAULT
Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
+Parallel DML: unsafe
insert into range_parted values ('c', 9);
-- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..11a750b58d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -339,6 +339,7 @@ Indexes:
"part_a_idx" btree (a), tablespace "regress_tblspace"
Partitions: testschema.part1 FOR VALUES IN (1),
testschema.part2 FOR VALUES IN (2)
+Parallel DML: unsafe
\d testschema.part1
Table "testschema.part1"
@@ -358,6 +359,7 @@ Partition of: testschema.part FOR VALUES IN (1)
Partition constraint: ((a IS NOT NULL) AND (a = 1))
Indexes:
"part1_a_idx" btree (a), tablespace "regress_tblspace"
+Parallel DML: unsafe
\d testschema.part_a_idx
Partitioned index "testschema.part_a_idx"
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..807f80927c
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,343 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+alter table testdef parallel dml safe;
+
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+begin;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+
+alter table para_insert_p1 parallel dml safe;
+alter table para_insert_f1 parallel dml safe;
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+select pg_get_max_parallel_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+select pg_get_max_parallel_hazard('names2');
+alter table names2 parallel dml safe;
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+select pg_get_max_parallel_hazard('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+select pg_get_max_parallel_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+alter table table_check_b parallel dml safe;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+select pg_get_max_parallel_hazard('table_check_b');
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+alter table names_with_unsafe_trigger parallel dml safe;
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+alter table names_with_unsafe_trigger parallel dml safe;
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+select pg_get_max_parallel_hazard('dom_table_u');
+
+rollback;
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
From: houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com>
Hi,
When testing the patch, I found some issues in the 0003,0004 patch.
Attaching new version patchset which fix these issues.0003
* don't check parallel safety of partition's default column expression.
* rename some function/variable names to be consistent with existing code.
* remove some unused function parameters.
* fix a max_hazard overwrite issue.
* add some code comments and adjust some code forms.0004
* Remove some unrelated comments in the regression test.
* add the 'begin;', 'rollback;' in insert_parallel.sql.
Through further review and thanks for greg-san's suggestions,
I attached a new version patchset with some minor change in 0001,0003 and 0004.
0001.
* fix a typo in variable name.
* add a TODO in patch comment about updating the version number when branch PG15.
0003
* fix a 'git apply white space' warning.
* Remove some unnecessary if condition.
* add some code comments above the safety check function.
* Fix some typo.
0004
* add a testcase to test ALTER PARALLEL DML UNSAFE/RESTRICTED.
Best regards,
houzj
Attachments:
v10-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v10-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 57e72e841f87ba4380f1abef88e76ed4609e4348 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Wed, 2 Jun 2021 11:14:26 +0800
Subject: [PATCH] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
TODO: Currently, use version number PG14(140000) to let the code to be
tested. The check like the following[1] need to be changed to PG15(150000)
when community group branch PG15.
[1] 'if (pset.sversion >= 140000)'
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 ++++++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 65 +++++++++++-----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 69 +++++++++++++++--
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../modules/test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 277 insertions(+), 39 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..88fcd57 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index afa830d..7b1152d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -302,6 +302,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -404,7 +405,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -961,6 +963,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1154,6 +1157,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1301,6 +1305,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 50b7a16..ce2ae5a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index bf81f6c..d0a9fea 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e..2151121 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce8820..45aacc8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9..6f25c23 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac..5d14b1e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -925,6 +928,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -943,6 +968,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4184,6 +4210,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4717,6 +4744,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5119,6 +5151,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18614,3 +18649,55 @@ GetAttributeCompression(Oid atttypid, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c..8baebe0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642db..2d77a88 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a8..6bf8787 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3531,6 +3531,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d09..aaed9d1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1284,6 +1285,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14..16c66a8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2703,6 +2704,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea0..09f5f0d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ee90e3..f099795 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -609,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2683,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3276,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3290,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3310,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3331,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3352,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3374,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3396,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4081,6 +4097,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4228,7 +4248,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4237,6 +4257,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5016,7 +5037,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5028,15 +5049,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5048,15 +5070,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5069,15 +5092,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5090,10 +5114,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15563,6 +15588,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16103,6 +16129,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615..ef6200d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1870,6 +1870,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3356,7 +3357,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3506,6 +3508,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7..abca220 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6227,6 +6227,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6331,7 +6332,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6423,7 +6424,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6476,7 +6477,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6529,7 +6530,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6582,7 +6583,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6633,7 +6634,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6681,7 +6682,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6729,7 +6730,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6776,7 +6777,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6845,6 +6846,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6900,6 +6902,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16423,6 +16426,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a..e083593 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 195f8d8..f6bab2f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1656,6 +1656,7 @@ describeOneTableDetails(const char *schemaname,
char *reloftype;
char relpersistence;
char relreplident;
+ char relparalleldml;
char *relam;
} tableinfo;
bool show_column_details = false;
@@ -1669,7 +1670,25 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
- if (pset.sversion >= 120000)
+ if (pset.sversion >= 140000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+ "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+ "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+ "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+ "c.relpersistence, c.relreplident, am.amname, c.relparalleldml\n"
+ "FROM pg_catalog.pg_class c\n "
+ "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+ "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+ "WHERE c.oid = '%s';",
+ (verbose ?
+ "pg_catalog.array_to_string(c.reloptions || "
+ "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+ : "''"),
+ oid);
+ }
+ else if (pset.sversion >= 120000)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1872,8 @@ describeOneTableDetails(const char *schemaname,
(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
else
tableinfo.relam = NULL;
+ tableinfo.relparalleldml = (pset.sversion >= 140000) ?
+ *(PQgetvalue(res, 0, 15)) : 0;
PQclear(res);
res = NULL;
@@ -3630,6 +3651,20 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
printTableAddFooter(&cont, buf.data);
}
+
+ if (verbose &&
+ (tableinfo.relkind == RELKIND_RELATION ||
+ tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+ tableinfo.relkind == RELKIND_FOREIGN_TABLE) &&
+ tableinfo.relparalleldml != 0)
+ {
+ printfPQExpBuffer(&buf, _("Parallel DML: %s"),
+ tableinfo.relparalleldml == 'u' ? "unsafe" :
+ tableinfo.relparalleldml == 'r' ? "restricted" :
+ tableinfo.relparalleldml == 's' ? "safe" :
+ "???");
+ printTableAddFooter(&cont, buf.data);
+ }
}
/* reloptions, if verbose */
@@ -4005,7 +4040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
PGresult *res;
printQueryOpt myopt = pset.popt;
int cols_so_far;
- bool translate_columns[] = {false, false, true, false, false, false, false, false, false};
+ bool translate_columns[] = {false, false, true, false, false, false, false, false, false, false};
/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
@@ -4073,22 +4108,42 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("unlogged"),
gettext_noop("Persistence"));
translate_columns[cols_so_far] = true;
+ cols_so_far++;
}
/*
- * We don't bother to count cols_so_far below here, as there's no need
- * to; this might change with future additions to the output columns.
- */
-
- /*
* Access methods exist for tables, materialized views and indexes.
* This has been introduced in PostgreSQL 12 for tables.
*/
if (pset.sversion >= 120000 && !pset.hide_tableam &&
(showTables || showMatViews || showIndexes))
+ {
appendPQExpBuffer(&buf,
",\n am.amname as \"%s\"",
gettext_noop("Access method"));
+ cols_so_far++;
+ }
+
+ /*
+ * Show whether the data in the relation is unsafe('u'),
+ * restricted('r'), or safe('s') can be modified in parallel mode.
+ * This has been introduced in PostgreSQL 15 for tables.
+ */
+ if (pset.sversion >= 140000)
+ {
+ appendPQExpBuffer(&buf,
+ ",\n CASE c.relparalleldml WHEN 'u' THEN '%s' WHEN 'r' THEN '%s' WHEN 's' THEN '%s' END as \"%s\"",
+ gettext_noop("unsafe"),
+ gettext_noop("restricted"),
+ gettext_noop("safe"),
+ gettext_noop("Parallel DML"));
+ translate_columns[cols_so_far] = true;
+ }
+
+ /*
+ * We don't bother to count cols_so_far below here, as there's no need
+ * to; this might change with future additions to the output columns.
+ */
/*
* As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..b599759 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729..af280b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342..dcdf6db 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2168,6 +2169,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d..6b532b0 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf..05222fa 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855..5ea225a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5..e1f5678 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.7.2.windows.1
v10-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v10-0002-parallel-SELECT-for-INSERT.patchDownload
From 49f57ad7508e2ee33fade8a1a7358b7c4ec09eaa Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:32:54 +0800
Subject: [PATCH 2/3] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++++
src/backend/executor/execMain.c | 3 ++
src/backend/optimizer/plan/planner.c | 21 ++++-----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4414459..2d68e46 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1015,6 +1015,32 @@ IsInParallelMode(void)
}
/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
+/*
* CommandCounterIncrement
*/
void
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4ba..ea685f0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4e..7736813 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 517712a..7c58c88 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f686..fd3f86b 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887..32b5656 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.7.2.windows.1
v10-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v10-0003-get-parallel-safety-functions.patchDownload
From 24339079047817d1bab86ad5c89a8d39c09d2323 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 9 Jun 2021 17:39:23 +0800
Subject: [PATCH 2/2] get-parallel-safety-functions
Provide a utility function "pg_get_parallel_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_max_parallel_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 647 ++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 ++++
src/backend/utils/cache/typcache.c | 17 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 787 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7c58c8809b..d800ead4e0 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all; /* whether collect all the unsafe/restricted objects */
+ List *objects; /* parallel unsafe/restricted objects */
+ PartitionDirectory partition_directory; /* partition descriptors */
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,23 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_all_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context,
+ bool recurse_partition);
+static bool target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context);
+static bool target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +181,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +655,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +710,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +744,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +761,65 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+static safety_object *
+make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+/* check_functions_in_node callback */
+static bool
+parallel_hazard_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object = make_safety_object(func_id,
+ ProcedureRelationId,
+ proparallel);
+ cont->objects = lappend(cont->objects, object);
+ }
+
+ return false;
+}
+
+static bool
+parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_hazard_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_parallel_hazard(domain->resulttype, context))
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_hazard_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -881,6 +975,557 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * target_rel_parallel_hazard
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+List*
+target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.objects = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_all_parallel_hazard_recurse(targetRel, &context, false);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.objects;
+}
+
+
+static bool
+target_rel_all_parallel_hazard_recurse(Relation rel,
+ max_parallel_hazard_context *context,
+ bool recurse_partition)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ {
+ safety_object *object = make_safety_object(rel->rd_rel->oid,
+ RelationRelationId,
+ PROPARALLEL_RESTRICTED);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef && !recurse_partition)
+ {
+ Node *defaultexpr;
+
+ defaultexpr = build_column_default(rel, attnum + 1);
+ if (parallel_hazard_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified relation's trigger data.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object,
+ *parent_object;
+
+ object = make_safety_object(tgfoid, ProcedureRelationId,
+ proparallel);
+ parent_object = make_safety_object(tgoid, TriggerRelationId,
+ proparallel);
+
+ context->objects = lappend(context->objects, object);
+ context->objects = lappend(context->objects, parent_object);
+ }
+ }
+
+ return false;
+}
+
+/*
+ * index_expr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the input index expression and index predicate.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context)
+{
+ int indnatts;
+ int nsupport;
+ int i;
+ Form_pg_index indexStruct;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ /* Check parallel-safety of index expression */
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_hazard_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+
+ /* Check parallel-safety of index predicate */
+ if (parallel_hazard_walker((Node *) ii_Predicate, context))
+ return true;
+
+ /* Check parallel-safety of any index AM support functions */
+ indnatts = IndexRelationGetNumberOfAttributes(index_rel);
+ nsupport = indnatts * index_rel->rd_indam->amsupport;
+
+ for (i = 0; i < nsupport; i++)
+ {
+ char proparallel;
+ Oid funcOid = index_rel->rd_support[i];
+
+ if (!OidIsValid(funcOid))
+ continue;
+
+ proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object;
+
+ object = make_safety_object(funcOid, ProcedureRelationId,
+ proparallel);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_index_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any existing index expressions or index predicate of a specified
+ * relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ char temp_hazard;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_parallel_hazard(index_rel,
+ ii_Expressions,
+ ii_Predicate,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+
+ object = make_safety_object(index_oid, IndexRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified DOMAIN type. Only any CHECK expressions are
+ * examined for parallel-safety.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+ char temp_hazard;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) r->check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+ Oid constr_oid = get_domain_constraint_oid(typid,
+ r->name,
+ false);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any partitions of a specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs,
+ *qual;
+
+ /* Check partition check expression */
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_hazard_walker((Node *) qual, context))
+ return true;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object;
+
+ object = make_safety_object(funcOid, ProcedureRelationId,
+ proparallel);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_hazard_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_all_parallel_hazard_recurse(part_rel,
+ context,
+ true);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any CHECK expressions or CHECK constraints related to the
+ * specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ char temp_hazard;
+ int i;
+ TupleDesc tupdesc;
+ List *temp_objects;
+ ConstrCheck *check;
+
+ tupdesc = RelationGetDescr(rel);
+
+ if (tupdesc->constr == NULL)
+ return false;
+
+ check = tupdesc->constr->check;
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ if (context->objects != NIL)
+ {
+ Oid constr_oid;
+ safety_object *object;
+
+ constr_oid = get_relation_constraint_oid(rel->rd_rel->oid,
+ check->ccname,
+ true);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+}
+
/*
* is_parallel_allowed_for_modify
*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4dfd7..a62e305434 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_max_parallel_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_parallel_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index de96e96c8f..236c979666 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2518,6 +2518,23 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+/*
+ * GetDomainConstraints --- get DomainConstraintState list of specified domain type
+ */
+List *
+GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4607..2f8c52814a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_parallel_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_parallel_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_max_parallel_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_max_parallel_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b56565e5..67e8f5026a 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a4b7..28ca7d8a6e 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.18.4
v10-0004-regression-test-and-doc-updates.patchapplication/octet-stream; name=v10-0004-regression-test-and-doc-updates.patchDownload
From df25550d081d7466e8e0cdb57ef213a56747020b Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Wed, 9 Jun 2021 17:39:07 +0800
Subject: [PATCH 1/2] regression-test-and-doc-updates
---
contrib/test_decoding/expected/ddl.out | 4 +
doc/src/sgml/func.sgml | 61 ++
doc/src/sgml/ref/alter_foreign_table.sgml | 13 +
doc/src/sgml/ref/alter_table.sgml | 12 +
doc/src/sgml/ref/create_foreign_table.sgml | 37 ++
doc/src/sgml/ref/create_table.sgml | 38 ++
doc/src/sgml/ref/create_table_as.sgml | 23 +
src/test/regress/expected/alter_table.out | 2 +
src/test/regress/expected/compression_1.out | 9 +
src/test/regress/expected/copy2.out | 1 +
src/test/regress/expected/create_table.out | 14 +
.../regress/expected/create_table_like.out | 8 +
src/test/regress/expected/domain.out | 2 +
src/test/regress/expected/foreign_data.out | 42 ++
src/test/regress/expected/identity.out | 1 +
src/test/regress/expected/inherit.out | 13 +
src/test/regress/expected/insert.out | 12 +
src/test/regress/expected/insert_parallel.out | 585 ++++++++++++++++++
src/test/regress/expected/psql.out | 58 +-
src/test/regress/expected/publication.out | 4 +
.../regress/expected/replica_identity.out | 1 +
src/test/regress/expected/rowsecurity.out | 1 +
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats_ext.out | 1 +
src/test/regress/expected/update.out | 1 +
src/test/regress/output/tablespace.source | 2 +
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 344 ++++++++++
28 files changed, 1266 insertions(+), 27 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 4ff0044c78..1d7eebb897 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -446,6 +446,7 @@ WITH (user_catalog_table = true)
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -460,6 +461,7 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
INSERT INTO replication_metadata(relation, options)
VALUES ('bar', ARRAY['a', 'b']);
@@ -473,6 +475,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -492,6 +495,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
rewritemeornot | integer | | | | plain | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=false
INSERT INTO replication_metadata(relation, options)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 08b07f561e..264db45b03 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23909,6 +23909,67 @@ SELECT collation for ('foo' COLLATE "de_DE");
Undefined objects are identified with <literal>NULL</literal> values.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_parallel_safety</primary>
+ </indexterm>
+ <function>pg_get_parallel_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+ <returnvalue>record</returnvalue>
+ ( <parameter>objid</parameter> <type>oid</type>,
+ <parameter>classid</parameter> <type>oid</type>,
+ <parameter>proparallel</parameter> <type>char</type> )
+ </para>
+ <para>
+ Returns a row containing enough information to uniquely identify the
+ parallel unsafe/restricted table-related objects from which the
+ table's parallel DML safety is determined. The user can use this
+ information during development in order to accurately declare a
+ table's parallel DML safety, or to identify any problematic objects
+ if parallel DML fails or behaves unexpectedly. Note that when the
+ use of an object-related parallel unsafe/restricted function is
+ detected, both the function OID and the object OID are returned.
+ <parameter>classid</parameter> is the OID of the system catalog
+ containing the object;
+ <parameter>objid</parameter> is the OID of the object itself.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_max_parallel_hazard</primary>
+ </indexterm>
+ <function>pg_get_max_parallel_hazard</function> ( <type>regclass</type> )
+ <returnvalue>char</returnvalue>
+ </para>
+ <para>
+ Returns the worst parallel DML safety hazard that can be found in the
+ given relation:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>s</literal> safe
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>r</literal> restricted
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>u</literal> unsafe
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ Users can use this function to do a quick check without caring about
+ specific parallel-related objects.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 7ca03f3ac9..c1652e8312 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -29,6 +29,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -299,6 +301,17 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See the similar form of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>
+ for more details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 939d3fe273..194d7a04a7 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -37,6 +37,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -1011,6 +1013,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See <link linkend="sql-createtable"><command>CREATE TABLE</command></link> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477efe58..bb5f582d2c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -290,6 +292,41 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table (e.g., functions in triggers/index expression/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_parallel_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_max_parallel_hazard()</function></link> for more information.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">server_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index c6d0a35e50..8e80b09bb3 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
@@ -1336,6 +1339,41 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-paralleldmlsafety">
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table
+ (e.g., functions in triggers/index expressions/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_parallel_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_max_parallel_hazard()</function></link> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab56c..71c932b048 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
AS <replaceable>query</replaceable>
[ WITH [ NO ] DATA ]
</synopsis>
@@ -223,6 +224,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>query</replaceable></term>
<listitem>
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f81bdf513b..e800a218b5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2203,6 +2203,7 @@ alter table test_storage alter column a set storage external;
b | integer | | | 0 | plain | |
Indexes:
"test_storage_idx" btree (b, a)
+Parallel DML: unsafe
\d+ test_storage_idx
Index "public.test_storage_idx"
@@ -4190,6 +4191,7 @@ ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
a | integer | | | | plain | |
Partition key: RANGE (a)
Number of partitions: 0
+Parallel DML: unsafe
-- constraint should be created
\d part_rp
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index aac96037fc..ca76a1d605 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -12,6 +12,7 @@ INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
ERROR: unsupported LZ4 compression method
@@ -51,6 +52,7 @@ SELECT * INTO cmmove1 FROM cmdata;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | text | | | | extended | | |
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmmove1;
pg_column_compression
@@ -138,6 +140,7 @@ CREATE TABLE cmdata2 (f1 int);
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
\d+ cmdata2
@@ -145,6 +148,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
\d+ cmdata2
@@ -152,6 +156,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
--changing column storage should not impact the compression method
--but the data should not be compressed
@@ -162,6 +167,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | pglz | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
\d+ cmdata2
@@ -169,6 +175,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | pglz | |
+Parallel DML: unsafe
INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
SELECT pg_column_compression(f1) FROM cmdata2;
@@ -249,6 +256,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmdata;
pg_column_compression
@@ -263,6 +271,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | | |
+Parallel DML: unsafe
-- test alter compression method for materialized views
ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c64f0719e7..df20bcb97b 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -517,6 +517,7 @@ alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
f1 | integer | | | | plain | |
Check constraints:
"check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+Parallel DML: unsafe
copy check_con_tbl from stdin;
NOTICE: input = {"f1":1}
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index ad89dd05c1..7a3a4bea25 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,6 +505,7 @@ Number of partitions: 0
b | text | | | | extended | |
Partition key: RANGE (((a + 1)), substr(b, 1, 5))
Number of partitions: 0
+Parallel DML: unsafe
INSERT INTO partitioned2 VALUES (1, 'hello');
ERROR: no partition of relation "partitioned2" found for row
@@ -518,6 +519,7 @@ CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO
b | text | | | | extended | |
Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+Parallel DML: unsafe
DROP TABLE partitioned, partitioned2;
-- check reference to partitioned table's rowtype in partition descriptor
@@ -559,6 +561,7 @@ select * from partitioned where partitioned = '(1,2)'::partitioned;
b | integer | | | | plain | |
Partition of: partitioned FOR VALUES IN ('(1,2)')
Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+Parallel DML: unsafe
drop table partitioned;
-- check that dependencies of partition columns are handled correctly
@@ -618,6 +621,7 @@ Partitions: part_null FOR VALUES IN (NULL),
part_p1 FOR VALUES IN (1),
part_p2 FOR VALUES IN (2),
part_p3 FOR VALUES IN (3)
+Parallel DML: unsafe
-- forbidden expressions for partition bound with list partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
@@ -1058,6 +1062,7 @@ drop table test_part_coll_posix;
b | integer | | not null | 1 | plain | |
Partition of: parted FOR VALUES IN ('b')
Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Parallel DML: unsafe
-- Both partition bound and partition key in describe output
\d+ part_c
@@ -1070,6 +1075,7 @@ Partition of: parted FOR VALUES IN ('c')
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
Partition key: RANGE (b)
Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+Parallel DML: unsafe
-- a level-2 partition's constraint will include the parent's expressions
\d+ part_c_1_10
@@ -1080,6 +1086,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
b | integer | | not null | 0 | plain | |
Partition of: part_c FOR VALUES FROM (1) TO (10)
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Parallel DML: unsafe
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
@@ -1114,6 +1121,7 @@ CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MI
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+Parallel DML: unsafe
DROP TABLE unbounded_range_part;
CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
@@ -1126,6 +1134,7 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALU
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Parallel DML: unsafe
CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
\d+ range_parted4_2
@@ -1137,6 +1146,7 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Parallel DML: unsafe
CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
\d+ range_parted4_3
@@ -1148,6 +1158,7 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Parallel DML: unsafe
DROP TABLE range_parted4;
-- user-defined operator class in partition key
@@ -1184,6 +1195,7 @@ SELECT obj_description('parted_col_comment'::regclass);
b | text | | | | extended | |
Partition key: LIST (a)
Number of partitions: 0
+Parallel DML: unsafe
DROP TABLE parted_col_comment;
-- list partitioning on array type column
@@ -1196,6 +1208,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
a | integer[] | | | | extended | |
Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+Parallel DML: unsafe
DROP TABLE arrlp;
-- partition on boolean column
@@ -1210,6 +1223,7 @@ create table boolspart_f partition of boolspart for values in (false);
Partition key: LIST (a)
Partitions: boolspart_f FOR VALUES IN (false),
boolspart_t FOR VALUES IN (true)
+Parallel DML: unsafe
drop table boolspart;
-- partitions mixing temporary and permanent relations
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 4dc5e6aa5f..af8de78bdd 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -333,6 +333,7 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
a | text | | not null | | main | |
b | text | | | | extended | |
c | text | | | | external | |
+Parallel DML: unsafe
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@@ -342,6 +343,7 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
a | text | | not null | | extended | | A
b | text | | | | extended | | B
c | text | | | | extended | | C
+Parallel DML: unsafe
CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -356,6 +358,7 @@ NOTICE: merging constraint "ctlt1_a_check" with inherited definition
Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
description
@@ -378,6 +381,7 @@ Check constraints:
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1,
ctlt3
+Parallel DML: unsafe
CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -395,6 +399,7 @@ Check constraints:
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
description
@@ -418,6 +423,7 @@ Check constraints:
Statistics objects:
"public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
"public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
+Parallel DML: unsafe
SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
relname | objsubid | description
@@ -458,6 +464,7 @@ Check constraints:
Statistics objects:
"public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
"public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
+Parallel DML: unsafe
DROP TABLE public.pg_attrdef;
-- Check that LIKE isn't confused when new table masks the old, either
@@ -480,6 +487,7 @@ Check constraints:
Statistics objects:
"ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
"ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
+Parallel DML: unsafe
ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..2419d96a33 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -276,6 +276,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
WHERE (dcomptable.d1).i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
@@ -413,6 +414,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
WHERE dcomptable.d1[1].i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 5385f98a0f..4f50410f39 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -731,6 +731,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
\det+
List of foreign tables
@@ -852,6 +853,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- can't change the column type if it's used elsewhere
CREATE TABLE use_ft1_column_type (x ft1);
@@ -1390,6 +1392,7 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1401,6 +1404,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2;
\d+ fd_pt1
@@ -1410,6 +1414,7 @@ DROP FOREIGN TABLE ft2;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Parallel DML: unsafe
CREATE FOREIGN TABLE ft2 (
c1 integer NOT NULL,
@@ -1425,6 +1430,7 @@ CREATE FOREIGN TABLE ft2 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
\d+ fd_pt1
@@ -1435,6 +1441,7 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1446,6 +1453,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
CREATE TABLE ct3() INHERITS(ft2);
CREATE FOREIGN TABLE ft3 (
@@ -1469,6 +1477,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1478,6 +1487,7 @@ Child tables: ct3,
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1488,6 +1498,7 @@ Inherits: ft2
c3 | date | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- add attributes recursively
ALTER TABLE fd_pt1 ADD COLUMN c4 integer;
@@ -1508,6 +1519,7 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1526,6 +1538,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1540,6 +1553,7 @@ Child tables: ct3,
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1555,6 +1569,7 @@ Inherits: ft2
c8 | integer | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- alter attributes recursively
ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0;
@@ -1582,6 +1597,7 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
c7 | integer | | | | plain | |
c8 | text | | | | external | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1600,6 +1616,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- drop attributes recursively
ALTER TABLE fd_pt1 DROP COLUMN c4;
@@ -1615,6 +1632,7 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1628,6 +1646,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- add constraints recursively
ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT;
@@ -1655,6 +1674,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1670,6 +1690,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2; -- ERROR
ERROR: cannot drop foreign table ft2 because other objects depend on it
@@ -1702,6 +1723,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1715,6 +1737,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- drop constraints recursively
ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE;
@@ -1732,6 +1755,7 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1746,6 +1770,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- VALIDATE CONSTRAINT need do nothing on foreign tables
ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
@@ -1759,6 +1784,7 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1773,6 +1799,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- changes name of an attribute recursively
ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1;
@@ -1790,6 +1817,7 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1804,6 +1832,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR
@@ -1853,6 +1882,7 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1865,6 +1895,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- partition cannot have additional columns
DROP FOREIGN TABLE fd_pt2_1;
@@ -1884,6 +1915,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c4 | character(1) | | | | | extended | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
@@ -1898,6 +1930,7 @@ DROP FOREIGN TABLE fd_pt2_1;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
CREATE FOREIGN TABLE fd_pt2_1 (
c1 integer NOT NULL,
@@ -1913,6 +1946,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- no attach partition validation occurs for foreign tables
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
@@ -1925,6 +1959,7 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1937,6 +1972,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot add column to a partition
ALTER TABLE fd_pt2_1 ADD c4 char;
@@ -1953,6 +1989,7 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1967,6 +2004,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot drop inherited NOT NULL constraint from a partition
ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
@@ -1983,6 +2021,7 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1995,6 +2034,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: column "c2" in child table must be marked NOT NULL
@@ -2013,6 +2053,7 @@ Partition key: LIST (c1)
Check constraints:
"fd_pt2chk1" CHECK (c1 > 0)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -2025,6 +2066,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: child table is missing constraint "fd_pt2chk1"
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7..da24c16d09 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -506,6 +506,7 @@ TABLE itest8;
f3 | integer | | not null | generated by default as identity | plain | |
f4 | bigint | | not null | generated always as identity | plain | |
f5 | bigint | | | | plain | |
+Parallel DML: unsafe
\d itest8_f2_seq
Sequence "public.itest8_f2_seq"
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 06f44287bc..33a216ea4d 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1059,6 +1059,7 @@ ALTER TABLE inhts RENAME d TO dd;
dd | integer | | | | plain | |
Inherits: inht1,
inhs1
+Parallel DML: unsafe
DROP TABLE inhts;
-- Test for renaming in diamond inheritance
@@ -1079,6 +1080,7 @@ ALTER TABLE inht1 RENAME aa TO aaa;
z | integer | | | | plain | |
Inherits: inht2,
inht3
+Parallel DML: unsafe
CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
NOTICE: merging multiple inherited definitions of column "b"
@@ -1096,6 +1098,7 @@ ERROR: cannot rename inherited column "b"
d | integer | | | | plain | |
Inherits: inht2,
inhs1
+Parallel DML: unsafe
WITH RECURSIVE r AS (
SELECT 'inht1'::regclass AS inhrelid
@@ -1142,6 +1145,7 @@ CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
Indexes:
"test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
Child tables: test_constraints_inh
+Parallel DML: unsafe
ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
\d+ test_constraints
@@ -1152,6 +1156,7 @@ ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Child tables: test_constraints_inh
+Parallel DML: unsafe
\d+ test_constraints_inh
Table "public.test_constraints_inh"
@@ -1161,6 +1166,7 @@ Child tables: test_constraints_inh
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Inherits: test_constraints
+Parallel DML: unsafe
DROP TABLE test_constraints_inh;
DROP TABLE test_constraints;
@@ -1177,6 +1183,7 @@ CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
Indexes:
"test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
\d+ test_ex_constraints
@@ -1185,6 +1192,7 @@ ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
\d+ test_ex_constraints_inh
Table "public.test_ex_constraints_inh"
@@ -1192,6 +1200,7 @@ Child tables: test_ex_constraints_inh
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Inherits: test_ex_constraints
+Parallel DML: unsafe
DROP TABLE test_ex_constraints_inh;
DROP TABLE test_ex_constraints;
@@ -1208,6 +1217,7 @@ Indexes:
"test_primary_constraints_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Parallel DML: unsafe
\d+ test_foreign_constraints
Table "public.test_foreign_constraints"
@@ -1217,6 +1227,7 @@ Referenced by:
Foreign-key constraints:
"test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
\d+ test_foreign_constraints
@@ -1225,6 +1236,7 @@ ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
\d+ test_foreign_constraints_inh
Table "public.test_foreign_constraints_inh"
@@ -1232,6 +1244,7 @@ Child tables: test_foreign_constraints_inh
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Inherits: test_foreign_constraints
+Parallel DML: unsafe
DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..cb8cd958aa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -177,6 +177,7 @@ Rules:
irule3 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
new.f2
+Parallel DML: unsafe
drop table inserttest2;
drop table inserttest;
@@ -482,6 +483,7 @@ Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
part_null FOR VALUES IN (NULL),
part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
part_default DEFAULT, PARTITIONED
+Parallel DML: unsafe
-- cleanup
drop table range_parted, list_parted;
@@ -497,6 +499,7 @@ create table part_default partition of list_parted default;
a | integer | | | | plain | |
Partition of: list_parted DEFAULT
No partition constraint
+Parallel DML: unsafe
insert into part_default values (null);
insert into part_default values (1);
@@ -888,6 +891,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Parallel DML: unsafe
\d+ mcrparted1_lt_b
Table "public.mcrparted1_lt_b"
@@ -897,6 +901,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+Parallel DML: unsafe
\d+ mcrparted2_b
Table "public.mcrparted2_b"
@@ -906,6 +911,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+Parallel DML: unsafe
\d+ mcrparted3_c_to_common
Table "public.mcrparted3_c_to_common"
@@ -915,6 +921,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+Parallel DML: unsafe
\d+ mcrparted4_common_lt_0
Table "public.mcrparted4_common_lt_0"
@@ -924,6 +931,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+Parallel DML: unsafe
\d+ mcrparted5_common_0_to_10
Table "public.mcrparted5_common_0_to_10"
@@ -933,6 +941,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+Parallel DML: unsafe
\d+ mcrparted6_common_ge_10
Table "public.mcrparted6_common_ge_10"
@@ -942,6 +951,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+Parallel DML: unsafe
\d+ mcrparted7_gt_common_lt_d
Table "public.mcrparted7_gt_common_lt_d"
@@ -951,6 +961,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+Parallel DML: unsafe
\d+ mcrparted8_ge_d
Table "public.mcrparted8_ge_d"
@@ -960,6 +971,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+Parallel DML: unsafe
insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
('comm', -10), ('common', -10), ('common', 0), ('common', 10),
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..9d62825e65
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,585 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+--
+-- END: setup some tables and data needed by the tests.
+--
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_max_parallel_hazard('para_insert_f1');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+--------------------------
+ Insert on para_insert_p1
+ -> Seq Scan on tenk1
+(2 rows)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names2');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_max_parallel_hazard('names4');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+-------------------------
+ Insert on names5
+ -> Seq Scan on names
+(2 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_max_parallel_hazard('temp_names');
+ pg_get_max_parallel_hazard
+----------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('table_check_b');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_max_parallel_hazard('dom_table_u');
+ pg_get_max_parallel_hazard
+----------------------------
+ u
+(1 row)
+
+rollback;
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1901673622 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2818,6 +2818,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2825,6 +2826,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\set HIDE_TABLEAM off
\d+ tbl_heap_psql
@@ -2834,6 +2836,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap_psql
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2842,50 +2845,51 @@ Access method: heap_psql
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap
+Parallel DML: unsafe
-- AM is displayed for tables, indexes and materialized views.
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | | unsafe | 0 bytes |
(4 rows)
\dt+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+---------------+-------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(2 rows)
\dm+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(1 row)
-- But not for views and sequences.
\dv+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+----------------+------+----------------------+-------------+---------+-------------
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+----------------+------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(1 row)
\set HIDE_TABLEAM on
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(4 rows)
RESET ROLE;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..314ec05dc1 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -83,6 +83,7 @@ Indexes:
"testpub_tbl2_pkey" PRIMARY KEY, btree (id)
Publications:
"testpub_foralltables"
+Parallel DML: unsafe
\dRp+ testpub_foralltables
Publication testpub_foralltables
@@ -196,6 +197,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\d+ testpub_tbl1
Table "public.testpub_tbl1"
@@ -209,6 +211,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\dRp+ testpub_default
Publication testpub_default
@@ -234,6 +237,7 @@ Indexes:
Publications:
"testpib_ins_trunct"
"testpub_fortbl"
+Parallel DML: unsafe
-- permissions
SET ROLE regress_publication_user2;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..0f8718f2a4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -171,6 +171,7 @@ Indexes:
"test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
"test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
Replica Identity: FULL
+Parallel DML: unsafe
ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 367ecace47..369bea2e65 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -958,6 +958,7 @@ Policies:
Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
part_document_nonfiction FOR VALUES FROM (99) TO (100),
part_document_satire FOR VALUES FROM (55) TO (56)
+Parallel DML: unsafe
SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..cad4be0a8b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3155,6 +3155,7 @@ Rules:
r3 AS
ON DELETE TO rules_src DO
NOTIFY rules_src_deletion
+Parallel DML: unsafe
--
-- Ensure an aliased target relation for insert is correctly deparsed.
@@ -3183,6 +3184,7 @@ Rules:
r5 AS
ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text
WHERE trgt.f1 = new.f1
+Parallel DML: unsafe
--
-- Also check multiassignment deparsing.
@@ -3206,6 +3208,7 @@ Rules:
WHERE trgt.f1 = new.f1
RETURNING new.f1,
new.f2
+Parallel DML: unsafe
drop table rule_t1, rule_dest;
--
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c214d8dfc..c94eb3293e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -145,6 +145,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
b | integer | | | | plain | |
Statistics objects:
"public"."ab1_a_b_stats" ON a, b FROM ab1
+Parallel DML: unsafe
-- partial analyze doesn't build stats either
ANALYZE ab1 (a);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index c809f88f54..3fcd8e10f3 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -753,6 +753,7 @@ create table part_def partition of range_parted default;
e | character varying | | | | extended | |
Partition of: range_parted DEFAULT
Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
+Parallel DML: unsafe
insert into range_parted values ('c', 9);
-- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..11a750b58d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -339,6 +339,7 @@ Indexes:
"part_a_idx" btree (a), tablespace "regress_tblspace"
Partitions: testschema.part1 FOR VALUES IN (1),
testschema.part2 FOR VALUES IN (2)
+Parallel DML: unsafe
\d testschema.part1
Table "testschema.part1"
@@ -358,6 +359,7 @@ Partition of: testschema.part FOR VALUES IN (1)
Partition constraint: ((a IS NOT NULL) AND (a = 1))
Indexes:
"part1_a_idx" btree (a), tablespace "regress_tblspace"
+Parallel DML: unsafe
\d testschema.part_a_idx
Partitioned index "testschema.part_a_idx"
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..ba8a6a6698
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,344 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+begin;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('para_insert_f1');
+select pg_get_max_parallel_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names2');
+select pg_get_max_parallel_hazard('names2');
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names4');
+select pg_get_max_parallel_hazard('names4');
+
+--
+-- Test INSERT with underlying 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 returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('temp_names');
+select pg_get_max_parallel_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('table_check_b');
+select pg_get_max_parallel_hazard('table_check_b');
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_safe_trigger');
+select pg_get_max_parallel_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('names_with_unsafe_trigger');
+select pg_get_max_parallel_hazard('names_with_unsafe_trigger');
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('part_unsafe_trigger');
+select pg_get_max_parallel_hazard('part_unsafe_trigger');
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_parallel_safety('dom_table_u');
+select pg_get_max_parallel_hazard('dom_table_u');
+
+rollback;
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
On Thu, Jun 10, 2021 at 11:26 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Through further review and thanks for greg-san's suggestions,
I attached a new version patchset with some minor change in 0001,0003 and 0004.0001.
* fix a typo in variable name.
* add a TODO in patch comment about updating the version number when branch PG15.0003
* fix a 'git apply white space' warning.
* Remove some unnecessary if condition.
* add some code comments above the safety check function.
* Fix some typo.0004
* add a testcase to test ALTER PARALLEL DML UNSAFE/RESTRICTED.
Thanks, those updates addressed most of what I was going to comment
on for the v9 patches.
Some additional comments on the v10 patches:
(1) I noticed some functions in the 0003 patch have no function header:
make_safety_object
parallel_hazard_walker
target_rel_all_parallel_hazard_recurse
(2) I found the "recurse_partition" parameter of the
target_rel_all_parallel_hazard_recurse() function a bit confusing,
because the function recursively checks partitions without looking at
that flag. How about naming it "is_partition"?
(3) The names of the utility functions don't convey that they operate on tables.
How about:
pg_get_parallel_safety() -> pg_get_table_parallel_safety()
pg_get_max_parallel_hazard() -> pg_get_table_max_parallel_hazard()
or pg_get_rel_xxxxx()?
What do you think?
(4) I think that some of the tests need parallel dml settings to match
their expected output:
(i)
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
-> but creates a serial plan (so needs to set parallel dml safe, so a
parallel plan is created)
(ii)
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
-> should set "unsafe" not "safe"
(iii)
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
-> should use "alter table testdef parallel dml restricted;" before the explain
(iv)
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should
not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
-> should use "alter table testdef parallel dml unsafe;" before the explain
Regards,
Greg Nancarrow
Fujitsu Australia
On Thursday, June 10, 2021 1:39 PM Greg Nancarrow <gregn4422@gmail.com> wrote:
On Thu, Jun 10, 2021 at 11:26 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:Through further review and thanks for greg-san's suggestions, I
attached a new version patchset with some minor change in 0001,0003 and0004.
0001.
* fix a typo in variable name.
* add a TODO in patch comment about updating the version number whenbranch PG15.
0003
* fix a 'git apply white space' warning.
* Remove some unnecessary if condition.
* add some code comments above the safety check function.
* Fix some typo.0004
* add a testcase to test ALTER PARALLEL DML UNSAFE/RESTRICTED.Thanks, those updates addressed most of what I was going to comment on
for the v9 patches.Some additional comments on the v10 patches:
(1) I noticed some functions in the 0003 patch have no function header:
make_safety_object
parallel_hazard_walker
target_rel_all_parallel_hazard_recurse
Thanks, added.
(2) I found the "recurse_partition" parameter of the
target_rel_all_parallel_hazard_recurse() function a bit confusing, because the
function recursively checks partitions without looking at that flag. How about
naming it "is_partition"?
Yeah, it looks better. Changed.
(3) The names of the utility functions don't convey that they operate on tables.
How about:
pg_get_parallel_safety() -> pg_get_table_parallel_safety()
pg_get_max_parallel_hazard() -> pg_get_table_max_parallel_hazard()or pg_get_rel_xxxxx()?
What do you think?
I changed it like the following:
pg_get_parallel_safety -> pg_get_table_parallel_dml_safety
pg_get_max_parallel_hazard -> pg_get_table_max_parallel_dml_hazard
(4) I think that some of the tests need parallel dml settings to match their
expected output:(i) +-- Test INSERT with underlying query - and RETURNING (no projection) +-- (should create a parallel plan; parallel SELECT)-> but creates a serial plan (so needs to set parallel dml safe, so a
parallel plan is created)
Changed.
(ii) +-- Parallel INSERT with unsafe column default, should not use a +parallel plan +-- +alter table testdef parallel dml safe;-> should set "unsafe" not "safe"
I thought the testcase about table 'testdef' is to test if the planner is able to
check whether it has parallel unsafe or restricted column default expression,
because column default expression will be merged into select part in planner.
So, It seems we don't need to change the table's parallel safety for these cases ?
(iii) +-- Parallel INSERT with restricted column default, should use parallel +SELECT +-- +explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from +test_data;-> should use "alter table testdef parallel dml restricted;" before the
-> explain(iv) +-- +-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan +-- +explain (costs off) insert into testdef(a,d) select a,a*8 from +test_data;-> should use "alter table testdef parallel dml unsafe;" before the
-> explain
I addressed most of the comments and rebased the patch.
Besides, I changed the following things:
* I removed the safety check for index-am function as discussed[1]/messages/by-id/164474.1623652853@sss.pgh.pa.us.
* change version 140000 to 150000
Attach new version patchset for further review.
[1]: /messages/by-id/164474.1623652853@sss.pgh.pa.us
Best regards,
houzj
Attachments:
v11-0004-regression-test-and-doc-updates.patchapplication/octet-stream; name=v11-0004-regression-test-and-doc-updates.patchDownload
From 2f2b6e0204a24b013ab0c5978dd907ebca95bfff Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 6 Jul 2021 11:02:39 +0800
Subject: [PATCH] 4
---
contrib/test_decoding/expected/ddl.out | 4 +
doc/src/sgml/func.sgml | 61 ++
doc/src/sgml/ref/alter_foreign_table.sgml | 13 +
doc/src/sgml/ref/alter_table.sgml | 12 +
doc/src/sgml/ref/create_foreign_table.sgml | 37 ++
doc/src/sgml/ref/create_table.sgml | 38 ++
doc/src/sgml/ref/create_table_as.sgml | 23 +
src/test/regress/expected/alter_table.out | 2 +
src/test/regress/expected/compression_1.out | 9 +
src/test/regress/expected/copy2.out | 1 +
src/test/regress/expected/create_table.out | 14 +
.../regress/expected/create_table_like.out | 8 +
src/test/regress/expected/domain.out | 2 +
src/test/regress/expected/foreign_data.out | 42 ++
src/test/regress/expected/identity.out | 1 +
src/test/regress/expected/inherit.out | 13 +
src/test/regress/expected/insert.out | 12 +
src/test/regress/expected/insert_parallel.out | 588 ++++++++++++++++++
src/test/regress/expected/psql.out | 58 +-
src/test/regress/expected/publication.out | 4 +
.../regress/expected/replica_identity.out | 1 +
src/test/regress/expected/rowsecurity.out | 1 +
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats_ext.out | 1 +
src/test/regress/expected/update.out | 1 +
src/test/regress/output/tablespace.source | 2 +
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 345 ++++++++++
28 files changed, 1270 insertions(+), 27 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 4ff0044c78..1d7eebb897 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -446,6 +446,7 @@ WITH (user_catalog_table = true)
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -460,6 +461,7 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
INSERT INTO replication_metadata(relation, options)
VALUES ('bar', ARRAY['a', 'b']);
@@ -473,6 +475,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -492,6 +495,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
rewritemeornot | integer | | | | plain | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=false
INSERT INTO replication_metadata(relation, options)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6388385edc..b496332217 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23909,6 +23909,67 @@ SELECT collation for ('foo' COLLATE "de_DE");
Undefined objects are identified with <literal>NULL</literal> values.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_table_parallel_dml_safety</primary>
+ </indexterm>
+ <function>pg_get_table_parallel_dml_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+ <returnvalue>record</returnvalue>
+ ( <parameter>objid</parameter> <type>oid</type>,
+ <parameter>classid</parameter> <type>oid</type>,
+ <parameter>proparallel</parameter> <type>char</type> )
+ </para>
+ <para>
+ Returns a row containing enough information to uniquely identify the
+ parallel unsafe/restricted table-related objects from which the
+ table's parallel DML safety is determined. The user can use this
+ information during development in order to accurately declare a
+ table's parallel DML safety, or to identify any problematic objects
+ if parallel DML fails or behaves unexpectedly. Note that when the
+ use of an object-related parallel unsafe/restricted function is
+ detected, both the function OID and the object OID are returned.
+ <parameter>classid</parameter> is the OID of the system catalog
+ containing the object;
+ <parameter>objid</parameter> is the OID of the object itself.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_table_max_parallel_dml_hazard</primary>
+ </indexterm>
+ <function>pg_get_table_max_parallel_dml_hazard</function> ( <type>regclass</type> )
+ <returnvalue>char</returnvalue>
+ </para>
+ <para>
+ Returns the worst parallel DML safety hazard that can be found in the
+ given relation:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>s</literal> safe
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>r</literal> restricted
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>u</literal> unsafe
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ Users can use this function to do a quick check without caring about
+ specific parallel-related objects.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 7ca03f3ac9..c1652e8312 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -29,6 +29,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -299,6 +301,17 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See the similar form of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>
+ for more details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c5e5e84e06..963c8cfcc6 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -37,6 +37,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -1010,6 +1012,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See <link linkend="sql-createtable"><command>CREATE TABLE</command></link> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477efe58..fbd5371eca 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -290,6 +292,41 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table (e.g., functions in triggers/index expression/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_parallel_dml_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">server_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..d6db677e3a 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
@@ -1336,6 +1339,41 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-paralleldmlsafety">
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table
+ (e.g., functions in triggers/index expressions/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_parallel_dml_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab56c..71c932b048 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
AS <replaceable>query</replaceable>
[ WITH [ NO ] DATA ]
</synopsis>
@@ -223,6 +224,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>query</replaceable></term>
<listitem>
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f81bdf513b..e800a218b5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2203,6 +2203,7 @@ alter table test_storage alter column a set storage external;
b | integer | | | 0 | plain | |
Indexes:
"test_storage_idx" btree (b, a)
+Parallel DML: unsafe
\d+ test_storage_idx
Index "public.test_storage_idx"
@@ -4190,6 +4191,7 @@ ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
a | integer | | | | plain | |
Partition key: RANGE (a)
Number of partitions: 0
+Parallel DML: unsafe
-- constraint should be created
\d part_rp
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index 1ce2962d55..041e829b37 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -12,6 +12,7 @@ INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
ERROR: compression method lz4 not supported
@@ -51,6 +52,7 @@ SELECT * INTO cmmove1 FROM cmdata;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | text | | | | extended | | |
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmmove1;
pg_column_compression
@@ -138,6 +140,7 @@ CREATE TABLE cmdata2 (f1 int);
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
\d+ cmdata2
@@ -145,6 +148,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
\d+ cmdata2
@@ -152,6 +156,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
--changing column storage should not impact the compression method
--but the data should not be compressed
@@ -162,6 +167,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | pglz | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
\d+ cmdata2
@@ -169,6 +175,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | pglz | |
+Parallel DML: unsafe
INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
SELECT pg_column_compression(f1) FROM cmdata2;
@@ -249,6 +256,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmdata;
pg_column_compression
@@ -263,6 +271,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | | |
+Parallel DML: unsafe
-- test alter compression method for materialized views
ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c64f0719e7..df20bcb97b 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -517,6 +517,7 @@ alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
f1 | integer | | | | plain | |
Check constraints:
"check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+Parallel DML: unsafe
copy check_con_tbl from stdin;
NOTICE: input = {"f1":1}
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 96bf426d98..13e2c1ca66 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,6 +505,7 @@ Number of partitions: 0
b | text | | | | extended | |
Partition key: RANGE (((a + 1)), substr(b, 1, 5))
Number of partitions: 0
+Parallel DML: unsafe
INSERT INTO partitioned2 VALUES (1, 'hello');
ERROR: no partition of relation "partitioned2" found for row
@@ -518,6 +519,7 @@ CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO
b | text | | | | extended | |
Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+Parallel DML: unsafe
DROP TABLE partitioned, partitioned2;
-- check reference to partitioned table's rowtype in partition descriptor
@@ -559,6 +561,7 @@ select * from partitioned where partitioned = '(1,2)'::partitioned;
b | integer | | | | plain | |
Partition of: partitioned FOR VALUES IN ('(1,2)')
Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+Parallel DML: unsafe
drop table partitioned;
-- check that dependencies of partition columns are handled correctly
@@ -618,6 +621,7 @@ Partitions: part_null FOR VALUES IN (NULL),
part_p1 FOR VALUES IN (1),
part_p2 FOR VALUES IN (2),
part_p3 FOR VALUES IN (3)
+Parallel DML: unsafe
-- forbidden expressions for partition bound with list partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
@@ -1064,6 +1068,7 @@ drop table test_part_coll_posix;
b | integer | | not null | 1 | plain | |
Partition of: parted FOR VALUES IN ('b')
Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Parallel DML: unsafe
-- Both partition bound and partition key in describe output
\d+ part_c
@@ -1076,6 +1081,7 @@ Partition of: parted FOR VALUES IN ('c')
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
Partition key: RANGE (b)
Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+Parallel DML: unsafe
-- a level-2 partition's constraint will include the parent's expressions
\d+ part_c_1_10
@@ -1086,6 +1092,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
b | integer | | not null | 0 | plain | |
Partition of: part_c FOR VALUES FROM (1) TO (10)
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Parallel DML: unsafe
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
@@ -1120,6 +1127,7 @@ CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MI
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+Parallel DML: unsafe
DROP TABLE unbounded_range_part;
CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
@@ -1132,6 +1140,7 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALU
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Parallel DML: unsafe
CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
\d+ range_parted4_2
@@ -1143,6 +1152,7 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Parallel DML: unsafe
CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
\d+ range_parted4_3
@@ -1154,6 +1164,7 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Parallel DML: unsafe
DROP TABLE range_parted4;
-- user-defined operator class in partition key
@@ -1190,6 +1201,7 @@ SELECT obj_description('parted_col_comment'::regclass);
b | text | | | | extended | |
Partition key: LIST (a)
Number of partitions: 0
+Parallel DML: unsafe
DROP TABLE parted_col_comment;
-- list partitioning on array type column
@@ -1202,6 +1214,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
a | integer[] | | | | extended | |
Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+Parallel DML: unsafe
DROP TABLE arrlp;
-- partition on boolean column
@@ -1216,6 +1229,7 @@ create table boolspart_f partition of boolspart for values in (false);
Partition key: LIST (a)
Partitions: boolspart_f FOR VALUES IN (false),
boolspart_t FOR VALUES IN (true)
+Parallel DML: unsafe
drop table boolspart;
-- partitions mixing temporary and permanent relations
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 4dc5e6aa5f..af8de78bdd 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -333,6 +333,7 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
a | text | | not null | | main | |
b | text | | | | extended | |
c | text | | | | external | |
+Parallel DML: unsafe
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@@ -342,6 +343,7 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
a | text | | not null | | extended | | A
b | text | | | | extended | | B
c | text | | | | extended | | C
+Parallel DML: unsafe
CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -356,6 +358,7 @@ NOTICE: merging constraint "ctlt1_a_check" with inherited definition
Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
description
@@ -378,6 +381,7 @@ Check constraints:
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1,
ctlt3
+Parallel DML: unsafe
CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -395,6 +399,7 @@ Check constraints:
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
description
@@ -418,6 +423,7 @@ Check constraints:
Statistics objects:
"public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
"public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
+Parallel DML: unsafe
SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
relname | objsubid | description
@@ -458,6 +464,7 @@ Check constraints:
Statistics objects:
"public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
"public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
+Parallel DML: unsafe
DROP TABLE public.pg_attrdef;
-- Check that LIKE isn't confused when new table masks the old, either
@@ -480,6 +487,7 @@ Check constraints:
Statistics objects:
"ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
"ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
+Parallel DML: unsafe
ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..2419d96a33 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -276,6 +276,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
WHERE (dcomptable.d1).i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
@@ -413,6 +414,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
WHERE dcomptable.d1[1].i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 5385f98a0f..4f50410f39 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -731,6 +731,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
\det+
List of foreign tables
@@ -852,6 +853,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- can't change the column type if it's used elsewhere
CREATE TABLE use_ft1_column_type (x ft1);
@@ -1390,6 +1392,7 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1401,6 +1404,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2;
\d+ fd_pt1
@@ -1410,6 +1414,7 @@ DROP FOREIGN TABLE ft2;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Parallel DML: unsafe
CREATE FOREIGN TABLE ft2 (
c1 integer NOT NULL,
@@ -1425,6 +1430,7 @@ CREATE FOREIGN TABLE ft2 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
\d+ fd_pt1
@@ -1435,6 +1441,7 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1446,6 +1453,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
CREATE TABLE ct3() INHERITS(ft2);
CREATE FOREIGN TABLE ft3 (
@@ -1469,6 +1477,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1478,6 +1487,7 @@ Child tables: ct3,
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1488,6 +1498,7 @@ Inherits: ft2
c3 | date | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- add attributes recursively
ALTER TABLE fd_pt1 ADD COLUMN c4 integer;
@@ -1508,6 +1519,7 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1526,6 +1538,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1540,6 +1553,7 @@ Child tables: ct3,
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1555,6 +1569,7 @@ Inherits: ft2
c8 | integer | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- alter attributes recursively
ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0;
@@ -1582,6 +1597,7 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
c7 | integer | | | | plain | |
c8 | text | | | | external | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1600,6 +1616,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- drop attributes recursively
ALTER TABLE fd_pt1 DROP COLUMN c4;
@@ -1615,6 +1632,7 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1628,6 +1646,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- add constraints recursively
ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT;
@@ -1655,6 +1674,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1670,6 +1690,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2; -- ERROR
ERROR: cannot drop foreign table ft2 because other objects depend on it
@@ -1702,6 +1723,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1715,6 +1737,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- drop constraints recursively
ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE;
@@ -1732,6 +1755,7 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1746,6 +1770,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- VALIDATE CONSTRAINT need do nothing on foreign tables
ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
@@ -1759,6 +1784,7 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1773,6 +1799,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- changes name of an attribute recursively
ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1;
@@ -1790,6 +1817,7 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1804,6 +1832,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR
@@ -1853,6 +1882,7 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1865,6 +1895,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- partition cannot have additional columns
DROP FOREIGN TABLE fd_pt2_1;
@@ -1884,6 +1915,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c4 | character(1) | | | | | extended | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
@@ -1898,6 +1930,7 @@ DROP FOREIGN TABLE fd_pt2_1;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
CREATE FOREIGN TABLE fd_pt2_1 (
c1 integer NOT NULL,
@@ -1913,6 +1946,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- no attach partition validation occurs for foreign tables
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
@@ -1925,6 +1959,7 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1937,6 +1972,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot add column to a partition
ALTER TABLE fd_pt2_1 ADD c4 char;
@@ -1953,6 +1989,7 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1967,6 +2004,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot drop inherited NOT NULL constraint from a partition
ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
@@ -1983,6 +2021,7 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1995,6 +2034,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: column "c2" in child table must be marked NOT NULL
@@ -2013,6 +2053,7 @@ Partition key: LIST (c1)
Check constraints:
"fd_pt2chk1" CHECK (c1 > 0)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -2025,6 +2066,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: child table is missing constraint "fd_pt2chk1"
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7..da24c16d09 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -506,6 +506,7 @@ TABLE itest8;
f3 | integer | | not null | generated by default as identity | plain | |
f4 | bigint | | not null | generated always as identity | plain | |
f5 | bigint | | | | plain | |
+Parallel DML: unsafe
\d itest8_f2_seq
Sequence "public.itest8_f2_seq"
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 06f44287bc..33a216ea4d 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1059,6 +1059,7 @@ ALTER TABLE inhts RENAME d TO dd;
dd | integer | | | | plain | |
Inherits: inht1,
inhs1
+Parallel DML: unsafe
DROP TABLE inhts;
-- Test for renaming in diamond inheritance
@@ -1079,6 +1080,7 @@ ALTER TABLE inht1 RENAME aa TO aaa;
z | integer | | | | plain | |
Inherits: inht2,
inht3
+Parallel DML: unsafe
CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
NOTICE: merging multiple inherited definitions of column "b"
@@ -1096,6 +1098,7 @@ ERROR: cannot rename inherited column "b"
d | integer | | | | plain | |
Inherits: inht2,
inhs1
+Parallel DML: unsafe
WITH RECURSIVE r AS (
SELECT 'inht1'::regclass AS inhrelid
@@ -1142,6 +1145,7 @@ CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
Indexes:
"test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
Child tables: test_constraints_inh
+Parallel DML: unsafe
ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
\d+ test_constraints
@@ -1152,6 +1156,7 @@ ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Child tables: test_constraints_inh
+Parallel DML: unsafe
\d+ test_constraints_inh
Table "public.test_constraints_inh"
@@ -1161,6 +1166,7 @@ Child tables: test_constraints_inh
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Inherits: test_constraints
+Parallel DML: unsafe
DROP TABLE test_constraints_inh;
DROP TABLE test_constraints;
@@ -1177,6 +1183,7 @@ CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
Indexes:
"test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
\d+ test_ex_constraints
@@ -1185,6 +1192,7 @@ ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
\d+ test_ex_constraints_inh
Table "public.test_ex_constraints_inh"
@@ -1192,6 +1200,7 @@ Child tables: test_ex_constraints_inh
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Inherits: test_ex_constraints
+Parallel DML: unsafe
DROP TABLE test_ex_constraints_inh;
DROP TABLE test_ex_constraints;
@@ -1208,6 +1217,7 @@ Indexes:
"test_primary_constraints_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Parallel DML: unsafe
\d+ test_foreign_constraints
Table "public.test_foreign_constraints"
@@ -1217,6 +1227,7 @@ Referenced by:
Foreign-key constraints:
"test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
\d+ test_foreign_constraints
@@ -1225,6 +1236,7 @@ ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
\d+ test_foreign_constraints_inh
Table "public.test_foreign_constraints_inh"
@@ -1232,6 +1244,7 @@ Child tables: test_foreign_constraints_inh
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Inherits: test_foreign_constraints
+Parallel DML: unsafe
DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..cb8cd958aa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -177,6 +177,7 @@ Rules:
irule3 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
new.f2
+Parallel DML: unsafe
drop table inserttest2;
drop table inserttest;
@@ -482,6 +483,7 @@ Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
part_null FOR VALUES IN (NULL),
part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
part_default DEFAULT, PARTITIONED
+Parallel DML: unsafe
-- cleanup
drop table range_parted, list_parted;
@@ -497,6 +499,7 @@ create table part_default partition of list_parted default;
a | integer | | | | plain | |
Partition of: list_parted DEFAULT
No partition constraint
+Parallel DML: unsafe
insert into part_default values (null);
insert into part_default values (1);
@@ -888,6 +891,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Parallel DML: unsafe
\d+ mcrparted1_lt_b
Table "public.mcrparted1_lt_b"
@@ -897,6 +901,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+Parallel DML: unsafe
\d+ mcrparted2_b
Table "public.mcrparted2_b"
@@ -906,6 +911,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+Parallel DML: unsafe
\d+ mcrparted3_c_to_common
Table "public.mcrparted3_c_to_common"
@@ -915,6 +921,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+Parallel DML: unsafe
\d+ mcrparted4_common_lt_0
Table "public.mcrparted4_common_lt_0"
@@ -924,6 +931,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+Parallel DML: unsafe
\d+ mcrparted5_common_0_to_10
Table "public.mcrparted5_common_0_to_10"
@@ -933,6 +941,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+Parallel DML: unsafe
\d+ mcrparted6_common_ge_10
Table "public.mcrparted6_common_ge_10"
@@ -942,6 +951,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+Parallel DML: unsafe
\d+ mcrparted7_gt_common_lt_d
Table "public.mcrparted7_gt_common_lt_d"
@@ -951,6 +961,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+Parallel DML: unsafe
\d+ mcrparted8_ge_d
Table "public.mcrparted8_ge_d"
@@ -960,6 +971,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+Parallel DML: unsafe
insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
('comm', -10), ('common', -10), ('common', 0), ('common', 10),
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..f5d22ce649
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,588 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+--
+-- END: setup some tables and data needed by the tests.
+--
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+--------------------------
+ Insert on para_insert_p1
+ -> Seq Scan on tenk1
+(2 rows)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names2');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names4');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+----------------------------------------
+ Insert on names5
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+rollback;
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1901673622 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2818,6 +2818,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2825,6 +2826,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\set HIDE_TABLEAM off
\d+ tbl_heap_psql
@@ -2834,6 +2836,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap_psql
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2842,50 +2845,51 @@ Access method: heap_psql
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap
+Parallel DML: unsafe
-- AM is displayed for tables, indexes and materialized views.
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | | unsafe | 0 bytes |
(4 rows)
\dt+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+---------------+-------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(2 rows)
\dm+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(1 row)
-- But not for views and sequences.
\dv+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+----------------+------+----------------------+-------------+---------+-------------
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+----------------+------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(1 row)
\set HIDE_TABLEAM on
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(4 rows)
RESET ROLE;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..314ec05dc1 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -83,6 +83,7 @@ Indexes:
"testpub_tbl2_pkey" PRIMARY KEY, btree (id)
Publications:
"testpub_foralltables"
+Parallel DML: unsafe
\dRp+ testpub_foralltables
Publication testpub_foralltables
@@ -196,6 +197,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\d+ testpub_tbl1
Table "public.testpub_tbl1"
@@ -209,6 +211,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\dRp+ testpub_default
Publication testpub_default
@@ -234,6 +237,7 @@ Indexes:
Publications:
"testpib_ins_trunct"
"testpub_fortbl"
+Parallel DML: unsafe
-- permissions
SET ROLE regress_publication_user2;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..0f8718f2a4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -171,6 +171,7 @@ Indexes:
"test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
"test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
Replica Identity: FULL
+Parallel DML: unsafe
ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 89397e41f0..396dc3a539 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -958,6 +958,7 @@ Policies:
Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
part_document_nonfiction FOR VALUES FROM (99) TO (100),
part_document_satire FOR VALUES FROM (55) TO (56)
+Parallel DML: unsafe
SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..cad4be0a8b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3155,6 +3155,7 @@ Rules:
r3 AS
ON DELETE TO rules_src DO
NOTIFY rules_src_deletion
+Parallel DML: unsafe
--
-- Ensure an aliased target relation for insert is correctly deparsed.
@@ -3183,6 +3184,7 @@ Rules:
r5 AS
ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text
WHERE trgt.f1 = new.f1
+Parallel DML: unsafe
--
-- Also check multiassignment deparsing.
@@ -3206,6 +3208,7 @@ Rules:
WHERE trgt.f1 = new.f1
RETURNING new.f1,
new.f2
+Parallel DML: unsafe
drop table rule_t1, rule_dest;
--
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c214d8dfc..c94eb3293e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -145,6 +145,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
b | integer | | | | plain | |
Statistics objects:
"public"."ab1_a_b_stats" ON a, b FROM ab1
+Parallel DML: unsafe
-- partial analyze doesn't build stats either
ANALYZE ab1 (a);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index c809f88f54..3fcd8e10f3 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -753,6 +753,7 @@ create table part_def partition of range_parted default;
e | character varying | | | | extended | |
Partition of: range_parted DEFAULT
Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
+Parallel DML: unsafe
insert into range_parted values ('c', 9);
-- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..11a750b58d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -339,6 +339,7 @@ Indexes:
"part_a_idx" btree (a), tablespace "regress_tblspace"
Partitions: testschema.part1 FOR VALUES IN (1),
testschema.part2 FOR VALUES IN (2)
+Parallel DML: unsafe
\d testschema.part1
Table "testschema.part1"
@@ -358,6 +359,7 @@ Partition of: testschema.part FOR VALUES IN (1)
Partition constraint: ((a IS NOT NULL) AND (a = 1))
Indexes:
"part1_a_idx" btree (a), tablespace "regress_tblspace"
+Parallel DML: unsafe
\d testschema.part_a_idx
Partitioned index "testschema.part_a_idx"
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..b4565f78ab
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,345 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+begin;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+select pg_get_table_max_parallel_dml_hazard('names2');
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+select pg_get_table_max_parallel_dml_hazard('names4');
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+
+rollback;
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
v11-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v11-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 57e72e841f87ba4380f1abef88e76ed4609e4348 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Wed, 2 Jun 2021 11:14:26 +0800
Subject: [PATCH] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 87 ++++++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 65 +++++++++++-----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 +++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 69 +++++++++++++++--
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../modules/test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 277 insertions(+), 39 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..88fcd57 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index afa830d..7b1152d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -302,6 +302,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -404,7 +405,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -961,6 +963,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1154,6 +1157,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1301,6 +1305,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 50b7a16..ce2ae5a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index bf81f6c..d0a9fea 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -253,6 +253,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e..2151121 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce8820..45aacc8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9..6f25c23 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac..5d14b1e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -602,6 +603,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -647,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -925,6 +928,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -943,6 +968,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4184,6 +4210,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4717,6 +4744,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5119,6 +5151,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -18614,3 +18649,55 @@ GetAttributeCompression(Oid atttypid, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot support foreign or temporary table data modification by parallel workers")));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c..8baebe0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642db..2d77a88 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90770a8..6bf8787 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3531,6 +3531,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ce76d09..aaed9d1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1284,6 +1285,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8da8b14..16c66a8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2703,6 +2704,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3772ea0..09f5f0d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ee90e3..f099795 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -609,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2683,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3276,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3290,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3310,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3331,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3352,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3374,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3396,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4081,6 +4097,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4228,7 +4248,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4237,6 +4257,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5016,7 +5037,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5028,15 +5049,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5048,15 +5070,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5069,15 +5092,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5090,10 +5114,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15563,6 +15588,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16103,6 +16129,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fd05615..ef6200d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1870,6 +1870,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3356,7 +3357,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3506,6 +3508,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f53cc7..abca220 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6227,6 +6227,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6331,7 +6332,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6423,7 +6424,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6476,7 +6477,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6529,7 +6530,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6582,7 +6583,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6633,7 +6634,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6681,7 +6682,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6729,7 +6730,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6776,7 +6777,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6845,6 +6846,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6900,6 +6902,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16423,6 +16426,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 49e1b0a..e083593 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -268,6 +268,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 195f8d8..f6bab2f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1656,6 +1656,7 @@ describeOneTableDetails(const char *schemaname,
char *reloftype;
char relpersistence;
char relreplident;
+ char relparalleldml;
char *relam;
} tableinfo;
bool show_column_details = false;
@@ -1669,7 +1670,25 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
- if (pset.sversion >= 120000)
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+ "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+ "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+ "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+ "c.relpersistence, c.relreplident, am.amname, c.relparalleldml\n"
+ "FROM pg_catalog.pg_class c\n "
+ "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+ "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+ "WHERE c.oid = '%s';",
+ (verbose ?
+ "pg_catalog.array_to_string(c.reloptions || "
+ "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+ : "''"),
+ oid);
+ }
+ else if (pset.sversion >= 120000)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1872,8 @@ describeOneTableDetails(const char *schemaname,
(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
else
tableinfo.relam = NULL;
+ tableinfo.relparalleldml = (pset.sversion >= 150000) ?
+ *(PQgetvalue(res, 0, 15)) : 0;
PQclear(res);
res = NULL;
@@ -3630,6 +3651,20 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
printTableAddFooter(&cont, buf.data);
}
+
+ if (verbose &&
+ (tableinfo.relkind == RELKIND_RELATION ||
+ tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+ tableinfo.relkind == RELKIND_FOREIGN_TABLE) &&
+ tableinfo.relparalleldml != 0)
+ {
+ printfPQExpBuffer(&buf, _("Parallel DML: %s"),
+ tableinfo.relparalleldml == 'u' ? "unsafe" :
+ tableinfo.relparalleldml == 'r' ? "restricted" :
+ tableinfo.relparalleldml == 's' ? "safe" :
+ "???");
+ printTableAddFooter(&cont, buf.data);
+ }
}
/* reloptions, if verbose */
@@ -4005,7 +4040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
PGresult *res;
printQueryOpt myopt = pset.popt;
int cols_so_far;
- bool translate_columns[] = {false, false, true, false, false, false, false, false, false};
+ bool translate_columns[] = {false, false, true, false, false, false, false, false, false, false};
/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
@@ -4073,22 +4108,42 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("unlogged"),
gettext_noop("Persistence"));
translate_columns[cols_so_far] = true;
+ cols_so_far++;
}
/*
- * We don't bother to count cols_so_far below here, as there's no need
- * to; this might change with future additions to the output columns.
- */
-
- /*
* Access methods exist for tables, materialized views and indexes.
* This has been introduced in PostgreSQL 12 for tables.
*/
if (pset.sversion >= 120000 && !pset.hide_tableam &&
(showTables || showMatViews || showIndexes))
+ {
appendPQExpBuffer(&buf,
",\n am.amname as \"%s\"",
gettext_noop("Access method"));
+ cols_so_far++;
+ }
+
+ /*
+ * Show whether the data in the relation is unsafe('u'),
+ * restricted('r'), or safe('s') can be modified in parallel mode.
+ * This has been introduced in PostgreSQL 15 for tables.
+ */
+ if (pset.sversion >= 150000)
+ {
+ appendPQExpBuffer(&buf,
+ ",\n CASE c.relparalleldml WHEN 'u' THEN '%s' WHEN 'r' THEN '%s' WHEN 's' THEN '%s' END as \"%s\"",
+ gettext_noop("unsafe"),
+ gettext_noop("restricted"),
+ gettext_noop("safe"),
+ gettext_noop("Parallel DML"));
+ translate_columns[cols_so_far] = true;
+ }
+
+ /*
+ * We don't bother to count cols_so_far below here, as there's no need
+ * to; this might change with future additions to the output columns.
+ */
/*
* As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..b599759 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 3e37729..af280b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef73342..dcdf6db 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2168,6 +2169,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 9ae851d..6b532b0 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf..05222fa 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855..5ea225a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5..e1f5678 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.7.2.windows.1
v11-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v11-0002-parallel-SELECT-for-INSERT.patchDownload
From 49f57ad7508e2ee33fade8a1a7358b7c4ec09eaa Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:32:54 +0800
Subject: [PATCH 2/3] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++++
src/backend/executor/execMain.c | 3 ++
src/backend/optimizer/plan/planner.c | 21 ++++-----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4414459..2d68e46 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1015,6 +1015,32 @@ IsInParallelMode(void)
}
/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
+/*
* CommandCounterIncrement
*/
void
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4ba..ea685f0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4e..7736813 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 517712a..7c58c88 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f686..fd3f86b 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887..32b5656 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.7.2.windows.1
v11-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v11-0003-get-parallel-safety-functions.patchDownload
From eed49aee131413d0785d68712c70e06874399e83 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Tue, 6 Jul 2021 11:23:45 +0800
Subject: [PATCH] get-parallel-safety-functions
Provide a utility function "pg_get_table_parallel_dml_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_table_max_parallel_dml_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 655 ++++++++++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 +++++
src/backend/utils/cache/typcache.c | 17 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 795 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7c58c88..406f54f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all; /* whether collect all the unsafe/restricted objects */
+ List *objects; /* parallel unsafe/restricted objects */
+ PartitionDirectory partition_directory; /* partition descriptors */
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,24 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context,
+ bool is_partition);
+static bool target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context);
+static bool target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition);
+static bool target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +182,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +656,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +711,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +745,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +762,82 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+/*
+ * make_safety_object
+ *
+ * Creates an safety_object given object id, class id and parallel safety.
+ */
+static safety_object *
+make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+/* check_functions_in_node callback */
+static bool
+parallel_hazard_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object = make_safety_object(func_id,
+ ProcedureRelationId,
+ proparallel);
+ cont->objects = lappend(cont->objects, object);
+ }
+
+ return false;
+}
+
+/*
+ * parallel_hazard_walker
+ *
+ * Recursively search an expression tree which is defined as partition key or
+ * index or constraint or column default expression for PARALLEL
+ * UNSAFE/RESTRICTED table-related objects.
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_hazard_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_parallel_hazard(domain->resulttype, context))
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_hazard_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -882,6 +994,547 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
+ * target_rel_parallel_hazard
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+List*
+target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.objects = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_parallel_hazard_recurse(targetRel, &context, false);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.objects;
+}
+
+/*
+ * target_rel_parallel_hazard_recurse
+ *
+ * Recursively search all table-related objects for PARALLEL UNSAFE/RESTRICTED
+ * objects.
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_parallel_hazard_recurse(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ {
+ safety_object *object = make_safety_object(rel->rd_rel->oid,
+ RelationRelationId,
+ PROPARALLEL_RESTRICTED);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_parallel_hazard(rel, context, is_partition))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef && !is_partition)
+ {
+ Node *defaultexpr;
+
+ defaultexpr = build_column_default(rel, attnum + 1);
+ if (parallel_hazard_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified relation's trigger data.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object,
+ *parent_object;
+
+ object = make_safety_object(tgfoid, ProcedureRelationId,
+ proparallel);
+ parent_object = make_safety_object(tgoid, TriggerRelationId,
+ proparallel);
+
+ context->objects = lappend(context->objects, object);
+ context->objects = lappend(context->objects, parent_object);
+ }
+ }
+
+ return false;
+}
+
+/*
+ * index_expr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the input index expression and index predicate.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ Form_pg_index indexStruct;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ /* Check parallel-safety of index expression */
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_hazard_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+
+ /* Check parallel-safety of index predicate */
+ if (parallel_hazard_walker((Node *) ii_Predicate, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_index_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any existing index expressions or index predicate of a specified
+ * relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ char temp_hazard;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_parallel_hazard(index_rel,
+ ii_Expressions,
+ ii_Predicate,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+
+ object = make_safety_object(index_oid, IndexRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified DOMAIN type. Only any CHECK expressions are
+ * examined for parallel-safety.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+ char temp_hazard;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) r->check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+ Oid constr_oid = get_domain_constraint_oid(typid,
+ r->name,
+ false);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any partitions of a specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs,
+ *qual;
+
+ /*
+ * The partition check expression is composed of its parent table's
+ * partition key expression, we do not need to check it again for a
+ * partition because we already checked the parallel safety of its parent
+ * table's partition key expression.
+ */
+ if (!is_partition)
+ {
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_hazard_walker((Node *) qual, context))
+ return true;
+ }
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object;
+
+ object = make_safety_object(funcOid, ProcedureRelationId,
+ proparallel);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_hazard_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_parallel_hazard_recurse(part_rel,
+ context,
+ true);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any CHECK expressions or CHECK constraints related to the
+ * specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ char temp_hazard;
+ int i;
+ TupleDesc tupdesc;
+ List *temp_objects;
+ ConstrCheck *check;
+
+ tupdesc = RelationGetDescr(rel);
+
+ if (tupdesc->constr == NULL)
+ return false;
+
+ check = tupdesc->constr->check;
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ if (context->objects != NIL)
+ {
+ Oid constr_oid;
+ safety_object *object;
+
+ constr_oid = get_relation_constraint_oid(rel->rd_rel->oid,
+ check->ccname,
+ true);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+}
+
+/*
* is_parallel_allowed_for_modify
*
* Check at a high-level if parallel mode is able to be used for the specified
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4d..06d859c 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_table_max_parallel_dml_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_table_parallel_dml_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index de96e96..236c979 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2519,6 +2519,23 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
}
/*
+ * GetDomainConstraints --- get DomainConstraintState list of specified domain type
+ */
+List *
+GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
+/*
* Load (or re-load) the enumData member of the typcache entry.
*/
static void
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4..274cd4f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_table_parallel_dml_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_table_parallel_dml_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_table_max_parallel_dml_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_table_max_parallel_dml_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b5656..67e8f50 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a..28ca7d8 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.7.2.windows.1
On Tuesday, July 6, 2021 11:42 AM houzj.fnst@fujitsu.com wrote:
I addressed most of the comments and rebased the patch.
Besides, I changed the following things:
* I removed the safety check for index-am function as discussed[1].
* change version 140000 to 150000Attach new version patchset for further review.
Attach a rebased patchset which fix some compile warnings
and errors due to recent commit 2ed532.
Best regards,
Hou zhijie
Attachments:
v12-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v12-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From ed74127ddb595eca9808931ee0877ae447621bbb Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Mon, 12 Jul 2021 15:45:43 +0800
Subject: [PATCH] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 93 ++++++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 65 ++++++++++-----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 ++++++++---
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 69 ++++++++++++++--
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../modules/test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 283 insertions(+), 39 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..88fcd57 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 09370a8..782eb58 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -302,6 +302,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -404,7 +405,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -961,6 +963,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1154,6 +1157,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1301,6 +1305,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 50b7a16..ce2ae5a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 147b5ab..b32d2d4 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -251,6 +251,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e..2151121 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 0982851..7607b91 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e3f9f6d..c7fff79 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 03dfd2e..c9faee5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -601,6 +602,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -646,6 +648,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -924,6 +927,30 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot perform parallel data modification on relation \"%s\"",
+ relname),
+ errdetail_relkind_not_supported(relkind)));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -942,6 +969,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4184,6 +4212,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4716,6 +4745,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5118,6 +5152,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -6075,6 +6112,8 @@ alter_table_type_to_string(AlterTableType cmdtype)
return "ALTER COLUMN ... DROP IDENTITY";
case AT_ReAddStatistics:
return NULL; /* not real grammar */
+ case AT_ParallelDMLSafety:
+ return "PARALLEL DML SAFETY";
}
return NULL;
@@ -18718,3 +18757,57 @@ GetAttributeCompression(Oid atttypid, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot perform parallel data modification on relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c..8baebe0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2540,6 +2540,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0..65f33a9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6fef067..76f1857 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3534,6 +3534,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9cc7b1..2705b61 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1292,6 +1293,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e09e4f7..dd64e0e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2714,6 +2715,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3dec0a2..aa66a22 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb24195..b8dc56f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -609,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2683,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3276,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3290,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3310,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3331,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3352,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3374,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3396,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4081,6 +4097,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4228,7 +4248,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4237,6 +4257,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5016,7 +5037,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5028,15 +5049,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5048,15 +5070,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5069,15 +5092,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5090,10 +5114,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15539,6 +15564,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16079,6 +16105,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 5dac9f0..160cc2e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1874,6 +1874,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3360,7 +3361,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3510,6 +3512,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3211521..53693ec 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6262,6 +6262,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6367,7 +6368,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6459,7 +6460,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6512,7 +6513,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6565,7 +6566,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6618,7 +6619,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6669,7 +6670,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6717,7 +6718,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6765,7 +6766,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6812,7 +6813,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6881,6 +6882,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6936,6 +6938,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16515,6 +16518,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index ba9bc6d..becdfc8 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -270,6 +270,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2abf255..04d32cd 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1656,6 +1656,7 @@ describeOneTableDetails(const char *schemaname,
char *reloftype;
char relpersistence;
char relreplident;
+ char relparalleldml;
char *relam;
} tableinfo;
bool show_column_details = false;
@@ -1669,7 +1670,25 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
- if (pset.sversion >= 120000)
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+ "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+ "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+ "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+ "c.relpersistence, c.relreplident, am.amname, c.relparalleldml\n"
+ "FROM pg_catalog.pg_class c\n "
+ "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+ "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+ "WHERE c.oid = '%s';",
+ (verbose ?
+ "pg_catalog.array_to_string(c.reloptions || "
+ "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+ : "''"),
+ oid);
+ }
+ else if (pset.sversion >= 120000)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1872,8 @@ describeOneTableDetails(const char *schemaname,
(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
else
tableinfo.relam = NULL;
+ tableinfo.relparalleldml = (pset.sversion >= 150000) ?
+ *(PQgetvalue(res, 0, 15)) : 0;
PQclear(res);
res = NULL;
@@ -3630,6 +3651,20 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
printTableAddFooter(&cont, buf.data);
}
+
+ if (verbose &&
+ (tableinfo.relkind == RELKIND_RELATION ||
+ tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+ tableinfo.relkind == RELKIND_FOREIGN_TABLE) &&
+ tableinfo.relparalleldml != 0)
+ {
+ printfPQExpBuffer(&buf, _("Parallel DML: %s"),
+ tableinfo.relparalleldml == 'u' ? "unsafe" :
+ tableinfo.relparalleldml == 'r' ? "restricted" :
+ tableinfo.relparalleldml == 's' ? "safe" :
+ "???");
+ printTableAddFooter(&cont, buf.data);
+ }
}
/* reloptions, if verbose */
@@ -4005,7 +4040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
PGresult *res;
printQueryOpt myopt = pset.popt;
int cols_so_far;
- bool translate_columns[] = {false, false, true, false, false, false, false, false, false};
+ bool translate_columns[] = {false, false, true, false, false, false, false, false, false, false};
/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
@@ -4073,22 +4108,42 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("unlogged"),
gettext_noop("Persistence"));
translate_columns[cols_so_far] = true;
+ cols_so_far++;
}
/*
- * We don't bother to count cols_so_far below here, as there's no need
- * to; this might change with future additions to the output columns.
- */
-
- /*
* Access methods exist for tables, materialized views and indexes.
* This has been introduced in PostgreSQL 12 for tables.
*/
if (pset.sversion >= 120000 && !pset.hide_tableam &&
(showTables || showMatViews || showIndexes))
+ {
appendPQExpBuffer(&buf,
",\n am.amname as \"%s\"",
gettext_noop("Access method"));
+ cols_so_far++;
+ }
+
+ /*
+ * Show whether the data in the relation is unsafe('u'),
+ * restricted('r'), or safe('s') can be modified in parallel mode.
+ * This has been introduced in PostgreSQL 15 for tables.
+ */
+ if (pset.sversion >= 150000)
+ {
+ appendPQExpBuffer(&buf,
+ ",\n CASE c.relparalleldml WHEN 'u' THEN '%s' WHEN 'r' THEN '%s' WHEN 's' THEN '%s' END as \"%s\"",
+ gettext_noop("unsafe"),
+ gettext_noop("restricted"),
+ gettext_noop("safe"),
+ gettext_noop("Parallel DML"));
+ translate_columns[cols_so_far] = true;
+ }
+
+ /*
+ * We don't bother to count cols_so_far below here, as there's no need
+ * to; this might change with future additions to the output columns.
+ */
/*
* As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..b599759 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945..4d37c6a 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651..f93679b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2179,6 +2180,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 996c3e4..1521373 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf..05222fa 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855..5ea225a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5..e1f5678 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.7.2.windows.1
v12-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v12-0002-parallel-SELECT-for-INSERT.patchDownload
From 49f57ad7508e2ee33fade8a1a7358b7c4ec09eaa Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:32:54 +0800
Subject: [PATCH 2/3] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++++
src/backend/executor/execMain.c | 3 ++
src/backend/optimizer/plan/planner.c | 21 ++++-----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4414459..2d68e46 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1015,6 +1015,32 @@ IsInParallelMode(void)
}
/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
+/*
* CommandCounterIncrement
*/
void
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4ba..ea685f0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4e..7736813 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 517712a..7c58c88 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f686..fd3f86b 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887..32b5656 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.7.2.windows.1
v12-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v12-0003-get-parallel-safety-functions.patchDownload
From eed49aee131413d0785d68712c70e06874399e83 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Tue, 6 Jul 2021 11:23:45 +0800
Subject: [PATCH] get-parallel-safety-functions
Provide a utility function "pg_get_table_parallel_dml_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_table_max_parallel_dml_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 655 ++++++++++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 +++++
src/backend/utils/cache/typcache.c | 17 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 795 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7c58c88..406f54f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all; /* whether collect all the unsafe/restricted objects */
+ List *objects; /* parallel unsafe/restricted objects */
+ PartitionDirectory partition_directory; /* partition descriptors */
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,24 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context,
+ bool is_partition);
+static bool target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context);
+static bool target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition);
+static bool target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +182,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +656,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +711,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +745,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +762,82 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+/*
+ * make_safety_object
+ *
+ * Creates an safety_object given object id, class id and parallel safety.
+ */
+static safety_object *
+make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+/* check_functions_in_node callback */
+static bool
+parallel_hazard_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object = make_safety_object(func_id,
+ ProcedureRelationId,
+ proparallel);
+ cont->objects = lappend(cont->objects, object);
+ }
+
+ return false;
+}
+
+/*
+ * parallel_hazard_walker
+ *
+ * Recursively search an expression tree which is defined as partition key or
+ * index or constraint or column default expression for PARALLEL
+ * UNSAFE/RESTRICTED table-related objects.
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_hazard_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_parallel_hazard(domain->resulttype, context))
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_hazard_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -882,6 +994,547 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
+ * target_rel_parallel_hazard
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+List*
+target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.objects = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_parallel_hazard_recurse(targetRel, &context, false);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.objects;
+}
+
+/*
+ * target_rel_parallel_hazard_recurse
+ *
+ * Recursively search all table-related objects for PARALLEL UNSAFE/RESTRICTED
+ * objects.
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_parallel_hazard_recurse(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ {
+ safety_object *object = make_safety_object(rel->rd_rel->oid,
+ RelationRelationId,
+ PROPARALLEL_RESTRICTED);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_parallel_hazard(rel, context, is_partition))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef && !is_partition)
+ {
+ Node *defaultexpr;
+
+ defaultexpr = build_column_default(rel, attnum + 1);
+ if (parallel_hazard_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified relation's trigger data.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object,
+ *parent_object;
+
+ object = make_safety_object(tgfoid, ProcedureRelationId,
+ proparallel);
+ parent_object = make_safety_object(tgoid, TriggerRelationId,
+ proparallel);
+
+ context->objects = lappend(context->objects, object);
+ context->objects = lappend(context->objects, parent_object);
+ }
+ }
+
+ return false;
+}
+
+/*
+ * index_expr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the input index expression and index predicate.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ Form_pg_index indexStruct;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ /* Check parallel-safety of index expression */
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_hazard_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+
+ /* Check parallel-safety of index predicate */
+ if (parallel_hazard_walker((Node *) ii_Predicate, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_index_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any existing index expressions or index predicate of a specified
+ * relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ char temp_hazard;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_parallel_hazard(index_rel,
+ ii_Expressions,
+ ii_Predicate,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+
+ object = make_safety_object(index_oid, IndexRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified DOMAIN type. Only any CHECK expressions are
+ * examined for parallel-safety.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+ char temp_hazard;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) r->check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+ Oid constr_oid = get_domain_constraint_oid(typid,
+ r->name,
+ false);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any partitions of a specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs,
+ *qual;
+
+ /*
+ * The partition check expression is composed of its parent table's
+ * partition key expression, we do not need to check it again for a
+ * partition because we already checked the parallel safety of its parent
+ * table's partition key expression.
+ */
+ if (!is_partition)
+ {
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_hazard_walker((Node *) qual, context))
+ return true;
+ }
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object;
+
+ object = make_safety_object(funcOid, ProcedureRelationId,
+ proparallel);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_hazard_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_parallel_hazard_recurse(part_rel,
+ context,
+ true);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any CHECK expressions or CHECK constraints related to the
+ * specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ char temp_hazard;
+ int i;
+ TupleDesc tupdesc;
+ List *temp_objects;
+ ConstrCheck *check;
+
+ tupdesc = RelationGetDescr(rel);
+
+ if (tupdesc->constr == NULL)
+ return false;
+
+ check = tupdesc->constr->check;
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ if (context->objects != NIL)
+ {
+ Oid constr_oid;
+ safety_object *object;
+
+ constr_oid = get_relation_constraint_oid(rel->rd_rel->oid,
+ check->ccname,
+ true);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+}
+
+/*
* is_parallel_allowed_for_modify
*
* Check at a high-level if parallel mode is able to be used for the specified
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4d..06d859c 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_table_max_parallel_dml_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_table_parallel_dml_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index de96e96..236c979 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2519,6 +2519,23 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
}
/*
+ * GetDomainConstraints --- get DomainConstraintState list of specified domain type
+ */
+List *
+GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
+/*
* Load (or re-load) the enumData member of the typcache entry.
*/
static void
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4..274cd4f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_table_parallel_dml_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_table_parallel_dml_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_table_max_parallel_dml_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_table_max_parallel_dml_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b5656..67e8f50 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a..28ca7d8 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.7.2.windows.1
v12-0004-regression-test-and-doc-updates.patchapplication/octet-stream; name=v12-0004-regression-test-and-doc-updates.patchDownload
From 2f2b6e0204a24b013ab0c5978dd907ebca95bfff Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 6 Jul 2021 11:02:39 +0800
Subject: [PATCH] regression-test-and-doc-updates
---
contrib/test_decoding/expected/ddl.out | 4 +
doc/src/sgml/func.sgml | 61 ++
doc/src/sgml/ref/alter_foreign_table.sgml | 13 +
doc/src/sgml/ref/alter_table.sgml | 12 +
doc/src/sgml/ref/create_foreign_table.sgml | 37 ++
doc/src/sgml/ref/create_table.sgml | 38 ++
doc/src/sgml/ref/create_table_as.sgml | 23 +
src/test/regress/expected/alter_table.out | 2 +
src/test/regress/expected/compression_1.out | 9 +
src/test/regress/expected/copy2.out | 1 +
src/test/regress/expected/create_table.out | 14 +
.../regress/expected/create_table_like.out | 8 +
src/test/regress/expected/domain.out | 2 +
src/test/regress/expected/foreign_data.out | 42 ++
src/test/regress/expected/identity.out | 1 +
src/test/regress/expected/inherit.out | 13 +
src/test/regress/expected/insert.out | 12 +
src/test/regress/expected/insert_parallel.out | 588 ++++++++++++++++++
src/test/regress/expected/psql.out | 58 +-
src/test/regress/expected/publication.out | 4 +
.../regress/expected/replica_identity.out | 1 +
src/test/regress/expected/rowsecurity.out | 1 +
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats_ext.out | 1 +
src/test/regress/expected/update.out | 1 +
src/test/regress/output/tablespace.source | 2 +
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 345 ++++++++++
28 files changed, 1270 insertions(+), 27 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 4ff0044c78..1d7eebb897 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -446,6 +446,7 @@ WITH (user_catalog_table = true)
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -460,6 +461,7 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
INSERT INTO replication_metadata(relation, options)
VALUES ('bar', ARRAY['a', 'b']);
@@ -473,6 +475,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -492,6 +495,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
rewritemeornot | integer | | | | plain | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=false
INSERT INTO replication_metadata(relation, options)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6388385edc..b496332217 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23909,6 +23909,67 @@ SELECT collation for ('foo' COLLATE "de_DE");
Undefined objects are identified with <literal>NULL</literal> values.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_table_parallel_dml_safety</primary>
+ </indexterm>
+ <function>pg_get_table_parallel_dml_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+ <returnvalue>record</returnvalue>
+ ( <parameter>objid</parameter> <type>oid</type>,
+ <parameter>classid</parameter> <type>oid</type>,
+ <parameter>proparallel</parameter> <type>char</type> )
+ </para>
+ <para>
+ Returns a row containing enough information to uniquely identify the
+ parallel unsafe/restricted table-related objects from which the
+ table's parallel DML safety is determined. The user can use this
+ information during development in order to accurately declare a
+ table's parallel DML safety, or to identify any problematic objects
+ if parallel DML fails or behaves unexpectedly. Note that when the
+ use of an object-related parallel unsafe/restricted function is
+ detected, both the function OID and the object OID are returned.
+ <parameter>classid</parameter> is the OID of the system catalog
+ containing the object;
+ <parameter>objid</parameter> is the OID of the object itself.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_table_max_parallel_dml_hazard</primary>
+ </indexterm>
+ <function>pg_get_table_max_parallel_dml_hazard</function> ( <type>regclass</type> )
+ <returnvalue>char</returnvalue>
+ </para>
+ <para>
+ Returns the worst parallel DML safety hazard that can be found in the
+ given relation:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>s</literal> safe
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>r</literal> restricted
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>u</literal> unsafe
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ Users can use this function to do a quick check without caring about
+ specific parallel-related objects.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 7ca03f3ac9..c1652e8312 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -29,6 +29,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -299,6 +301,17 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See the similar form of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>
+ for more details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c5e5e84e06..963c8cfcc6 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -37,6 +37,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -1010,6 +1012,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See <link linkend="sql-createtable"><command>CREATE TABLE</command></link> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477efe58..fbd5371eca 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -290,6 +292,41 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table (e.g., functions in triggers/index expression/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_parallel_dml_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">server_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..d6db677e3a 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
@@ -1336,6 +1339,41 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-paralleldmlsafety">
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table
+ (e.g., functions in triggers/index expressions/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_parallel_dml_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab56c..71c932b048 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
AS <replaceable>query</replaceable>
[ WITH [ NO ] DATA ]
</synopsis>
@@ -223,6 +224,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>query</replaceable></term>
<listitem>
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f81bdf513b..e800a218b5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2203,6 +2203,7 @@ alter table test_storage alter column a set storage external;
b | integer | | | 0 | plain | |
Indexes:
"test_storage_idx" btree (b, a)
+Parallel DML: unsafe
\d+ test_storage_idx
Index "public.test_storage_idx"
@@ -4190,6 +4191,7 @@ ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
a | integer | | | | plain | |
Partition key: RANGE (a)
Number of partitions: 0
+Parallel DML: unsafe
-- constraint should be created
\d part_rp
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index 1ce2962d55..041e829b37 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -12,6 +12,7 @@ INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
ERROR: compression method lz4 not supported
@@ -51,6 +52,7 @@ SELECT * INTO cmmove1 FROM cmdata;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | text | | | | extended | | |
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmmove1;
pg_column_compression
@@ -138,6 +140,7 @@ CREATE TABLE cmdata2 (f1 int);
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
\d+ cmdata2
@@ -145,6 +148,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
\d+ cmdata2
@@ -152,6 +156,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
--changing column storage should not impact the compression method
--but the data should not be compressed
@@ -162,6 +167,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | pglz | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
\d+ cmdata2
@@ -169,6 +175,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | pglz | |
+Parallel DML: unsafe
INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
SELECT pg_column_compression(f1) FROM cmdata2;
@@ -249,6 +256,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmdata;
pg_column_compression
@@ -263,6 +271,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | | |
+Parallel DML: unsafe
-- test alter compression method for materialized views
ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c64f0719e7..df20bcb97b 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -517,6 +517,7 @@ alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
f1 | integer | | | | plain | |
Check constraints:
"check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+Parallel DML: unsafe
copy check_con_tbl from stdin;
NOTICE: input = {"f1":1}
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 96bf426d98..13e2c1ca66 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,6 +505,7 @@ Number of partitions: 0
b | text | | | | extended | |
Partition key: RANGE (((a + 1)), substr(b, 1, 5))
Number of partitions: 0
+Parallel DML: unsafe
INSERT INTO partitioned2 VALUES (1, 'hello');
ERROR: no partition of relation "partitioned2" found for row
@@ -518,6 +519,7 @@ CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO
b | text | | | | extended | |
Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+Parallel DML: unsafe
DROP TABLE partitioned, partitioned2;
-- check reference to partitioned table's rowtype in partition descriptor
@@ -559,6 +561,7 @@ select * from partitioned where partitioned = '(1,2)'::partitioned;
b | integer | | | | plain | |
Partition of: partitioned FOR VALUES IN ('(1,2)')
Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+Parallel DML: unsafe
drop table partitioned;
-- check that dependencies of partition columns are handled correctly
@@ -618,6 +621,7 @@ Partitions: part_null FOR VALUES IN (NULL),
part_p1 FOR VALUES IN (1),
part_p2 FOR VALUES IN (2),
part_p3 FOR VALUES IN (3)
+Parallel DML: unsafe
-- forbidden expressions for partition bound with list partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
@@ -1064,6 +1068,7 @@ drop table test_part_coll_posix;
b | integer | | not null | 1 | plain | |
Partition of: parted FOR VALUES IN ('b')
Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Parallel DML: unsafe
-- Both partition bound and partition key in describe output
\d+ part_c
@@ -1076,6 +1081,7 @@ Partition of: parted FOR VALUES IN ('c')
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
Partition key: RANGE (b)
Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+Parallel DML: unsafe
-- a level-2 partition's constraint will include the parent's expressions
\d+ part_c_1_10
@@ -1086,6 +1092,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
b | integer | | not null | 0 | plain | |
Partition of: part_c FOR VALUES FROM (1) TO (10)
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Parallel DML: unsafe
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
@@ -1120,6 +1127,7 @@ CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MI
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+Parallel DML: unsafe
DROP TABLE unbounded_range_part;
CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
@@ -1132,6 +1140,7 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALU
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Parallel DML: unsafe
CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
\d+ range_parted4_2
@@ -1143,6 +1152,7 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Parallel DML: unsafe
CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
\d+ range_parted4_3
@@ -1154,6 +1164,7 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Parallel DML: unsafe
DROP TABLE range_parted4;
-- user-defined operator class in partition key
@@ -1190,6 +1201,7 @@ SELECT obj_description('parted_col_comment'::regclass);
b | text | | | | extended | |
Partition key: LIST (a)
Number of partitions: 0
+Parallel DML: unsafe
DROP TABLE parted_col_comment;
-- list partitioning on array type column
@@ -1202,6 +1214,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
a | integer[] | | | | extended | |
Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+Parallel DML: unsafe
DROP TABLE arrlp;
-- partition on boolean column
@@ -1216,6 +1229,7 @@ create table boolspart_f partition of boolspart for values in (false);
Partition key: LIST (a)
Partitions: boolspart_f FOR VALUES IN (false),
boolspart_t FOR VALUES IN (true)
+Parallel DML: unsafe
drop table boolspart;
-- partitions mixing temporary and permanent relations
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 4dc5e6aa5f..af8de78bdd 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -333,6 +333,7 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
a | text | | not null | | main | |
b | text | | | | extended | |
c | text | | | | external | |
+Parallel DML: unsafe
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@@ -342,6 +343,7 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
a | text | | not null | | extended | | A
b | text | | | | extended | | B
c | text | | | | extended | | C
+Parallel DML: unsafe
CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -356,6 +358,7 @@ NOTICE: merging constraint "ctlt1_a_check" with inherited definition
Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
description
@@ -378,6 +381,7 @@ Check constraints:
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1,
ctlt3
+Parallel DML: unsafe
CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -395,6 +399,7 @@ Check constraints:
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
description
@@ -418,6 +423,7 @@ Check constraints:
Statistics objects:
"public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
"public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
+Parallel DML: unsafe
SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
relname | objsubid | description
@@ -458,6 +464,7 @@ Check constraints:
Statistics objects:
"public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
"public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
+Parallel DML: unsafe
DROP TABLE public.pg_attrdef;
-- Check that LIKE isn't confused when new table masks the old, either
@@ -480,6 +487,7 @@ Check constraints:
Statistics objects:
"ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
"ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
+Parallel DML: unsafe
ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..2419d96a33 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -276,6 +276,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
WHERE (dcomptable.d1).i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
@@ -413,6 +414,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
WHERE dcomptable.d1[1].i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 5385f98a0f..4f50410f39 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -731,6 +731,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
\det+
List of foreign tables
@@ -852,6 +853,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- can't change the column type if it's used elsewhere
CREATE TABLE use_ft1_column_type (x ft1);
@@ -1390,6 +1392,7 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1401,6 +1404,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2;
\d+ fd_pt1
@@ -1410,6 +1414,7 @@ DROP FOREIGN TABLE ft2;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Parallel DML: unsafe
CREATE FOREIGN TABLE ft2 (
c1 integer NOT NULL,
@@ -1425,6 +1430,7 @@ CREATE FOREIGN TABLE ft2 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
\d+ fd_pt1
@@ -1435,6 +1441,7 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1446,6 +1453,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
CREATE TABLE ct3() INHERITS(ft2);
CREATE FOREIGN TABLE ft3 (
@@ -1469,6 +1477,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1478,6 +1487,7 @@ Child tables: ct3,
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1488,6 +1498,7 @@ Inherits: ft2
c3 | date | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- add attributes recursively
ALTER TABLE fd_pt1 ADD COLUMN c4 integer;
@@ -1508,6 +1519,7 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1526,6 +1538,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1540,6 +1553,7 @@ Child tables: ct3,
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1555,6 +1569,7 @@ Inherits: ft2
c8 | integer | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- alter attributes recursively
ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0;
@@ -1582,6 +1597,7 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
c7 | integer | | | | plain | |
c8 | text | | | | external | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1600,6 +1616,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- drop attributes recursively
ALTER TABLE fd_pt1 DROP COLUMN c4;
@@ -1615,6 +1632,7 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1628,6 +1646,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- add constraints recursively
ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT;
@@ -1655,6 +1674,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1670,6 +1690,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2; -- ERROR
ERROR: cannot drop foreign table ft2 because other objects depend on it
@@ -1702,6 +1723,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1715,6 +1737,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- drop constraints recursively
ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE;
@@ -1732,6 +1755,7 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1746,6 +1770,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- VALIDATE CONSTRAINT need do nothing on foreign tables
ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
@@ -1759,6 +1784,7 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1773,6 +1799,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- changes name of an attribute recursively
ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1;
@@ -1790,6 +1817,7 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1804,6 +1832,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR
@@ -1853,6 +1882,7 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1865,6 +1895,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- partition cannot have additional columns
DROP FOREIGN TABLE fd_pt2_1;
@@ -1884,6 +1915,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c4 | character(1) | | | | | extended | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
@@ -1898,6 +1930,7 @@ DROP FOREIGN TABLE fd_pt2_1;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
CREATE FOREIGN TABLE fd_pt2_1 (
c1 integer NOT NULL,
@@ -1913,6 +1946,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- no attach partition validation occurs for foreign tables
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
@@ -1925,6 +1959,7 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1937,6 +1972,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot add column to a partition
ALTER TABLE fd_pt2_1 ADD c4 char;
@@ -1953,6 +1989,7 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1967,6 +2004,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot drop inherited NOT NULL constraint from a partition
ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
@@ -1983,6 +2021,7 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1995,6 +2034,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: column "c2" in child table must be marked NOT NULL
@@ -2013,6 +2053,7 @@ Partition key: LIST (c1)
Check constraints:
"fd_pt2chk1" CHECK (c1 > 0)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -2025,6 +2066,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: child table is missing constraint "fd_pt2chk1"
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7..da24c16d09 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -506,6 +506,7 @@ TABLE itest8;
f3 | integer | | not null | generated by default as identity | plain | |
f4 | bigint | | not null | generated always as identity | plain | |
f5 | bigint | | | | plain | |
+Parallel DML: unsafe
\d itest8_f2_seq
Sequence "public.itest8_f2_seq"
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 06f44287bc..33a216ea4d 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1059,6 +1059,7 @@ ALTER TABLE inhts RENAME d TO dd;
dd | integer | | | | plain | |
Inherits: inht1,
inhs1
+Parallel DML: unsafe
DROP TABLE inhts;
-- Test for renaming in diamond inheritance
@@ -1079,6 +1080,7 @@ ALTER TABLE inht1 RENAME aa TO aaa;
z | integer | | | | plain | |
Inherits: inht2,
inht3
+Parallel DML: unsafe
CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
NOTICE: merging multiple inherited definitions of column "b"
@@ -1096,6 +1098,7 @@ ERROR: cannot rename inherited column "b"
d | integer | | | | plain | |
Inherits: inht2,
inhs1
+Parallel DML: unsafe
WITH RECURSIVE r AS (
SELECT 'inht1'::regclass AS inhrelid
@@ -1142,6 +1145,7 @@ CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
Indexes:
"test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
Child tables: test_constraints_inh
+Parallel DML: unsafe
ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
\d+ test_constraints
@@ -1152,6 +1156,7 @@ ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Child tables: test_constraints_inh
+Parallel DML: unsafe
\d+ test_constraints_inh
Table "public.test_constraints_inh"
@@ -1161,6 +1166,7 @@ Child tables: test_constraints_inh
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Inherits: test_constraints
+Parallel DML: unsafe
DROP TABLE test_constraints_inh;
DROP TABLE test_constraints;
@@ -1177,6 +1183,7 @@ CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
Indexes:
"test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
\d+ test_ex_constraints
@@ -1185,6 +1192,7 @@ ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
\d+ test_ex_constraints_inh
Table "public.test_ex_constraints_inh"
@@ -1192,6 +1200,7 @@ Child tables: test_ex_constraints_inh
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Inherits: test_ex_constraints
+Parallel DML: unsafe
DROP TABLE test_ex_constraints_inh;
DROP TABLE test_ex_constraints;
@@ -1208,6 +1217,7 @@ Indexes:
"test_primary_constraints_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Parallel DML: unsafe
\d+ test_foreign_constraints
Table "public.test_foreign_constraints"
@@ -1217,6 +1227,7 @@ Referenced by:
Foreign-key constraints:
"test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
\d+ test_foreign_constraints
@@ -1225,6 +1236,7 @@ ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
\d+ test_foreign_constraints_inh
Table "public.test_foreign_constraints_inh"
@@ -1232,6 +1244,7 @@ Child tables: test_foreign_constraints_inh
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Inherits: test_foreign_constraints
+Parallel DML: unsafe
DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..cb8cd958aa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -177,6 +177,7 @@ Rules:
irule3 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
new.f2
+Parallel DML: unsafe
drop table inserttest2;
drop table inserttest;
@@ -482,6 +483,7 @@ Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
part_null FOR VALUES IN (NULL),
part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
part_default DEFAULT, PARTITIONED
+Parallel DML: unsafe
-- cleanup
drop table range_parted, list_parted;
@@ -497,6 +499,7 @@ create table part_default partition of list_parted default;
a | integer | | | | plain | |
Partition of: list_parted DEFAULT
No partition constraint
+Parallel DML: unsafe
insert into part_default values (null);
insert into part_default values (1);
@@ -888,6 +891,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Parallel DML: unsafe
\d+ mcrparted1_lt_b
Table "public.mcrparted1_lt_b"
@@ -897,6 +901,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+Parallel DML: unsafe
\d+ mcrparted2_b
Table "public.mcrparted2_b"
@@ -906,6 +911,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+Parallel DML: unsafe
\d+ mcrparted3_c_to_common
Table "public.mcrparted3_c_to_common"
@@ -915,6 +921,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+Parallel DML: unsafe
\d+ mcrparted4_common_lt_0
Table "public.mcrparted4_common_lt_0"
@@ -924,6 +931,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+Parallel DML: unsafe
\d+ mcrparted5_common_0_to_10
Table "public.mcrparted5_common_0_to_10"
@@ -933,6 +941,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+Parallel DML: unsafe
\d+ mcrparted6_common_ge_10
Table "public.mcrparted6_common_ge_10"
@@ -942,6 +951,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+Parallel DML: unsafe
\d+ mcrparted7_gt_common_lt_d
Table "public.mcrparted7_gt_common_lt_d"
@@ -951,6 +961,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+Parallel DML: unsafe
\d+ mcrparted8_ge_d
Table "public.mcrparted8_ge_d"
@@ -960,6 +971,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+Parallel DML: unsafe
insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
('comm', -10), ('common', -10), ('common', 0), ('common', 10),
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..f5d22ce649
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,588 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+--
+-- END: setup some tables and data needed by the tests.
+--
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+--------------------------
+ Insert on para_insert_p1
+ -> Seq Scan on tenk1
+(2 rows)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names2');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names4');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+----------------------------------------
+ Insert on names5
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+rollback;
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1901673622 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2818,6 +2818,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2825,6 +2826,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\set HIDE_TABLEAM off
\d+ tbl_heap_psql
@@ -2834,6 +2836,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap_psql
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2842,50 +2845,51 @@ Access method: heap_psql
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap
+Parallel DML: unsafe
-- AM is displayed for tables, indexes and materialized views.
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | | unsafe | 0 bytes |
(4 rows)
\dt+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+---------------+-------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(2 rows)
\dm+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(1 row)
-- But not for views and sequences.
\dv+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+----------------+------+----------------------+-------------+---------+-------------
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+----------------+------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(1 row)
\set HIDE_TABLEAM on
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(4 rows)
RESET ROLE;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..314ec05dc1 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -83,6 +83,7 @@ Indexes:
"testpub_tbl2_pkey" PRIMARY KEY, btree (id)
Publications:
"testpub_foralltables"
+Parallel DML: unsafe
\dRp+ testpub_foralltables
Publication testpub_foralltables
@@ -196,6 +197,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\d+ testpub_tbl1
Table "public.testpub_tbl1"
@@ -209,6 +211,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\dRp+ testpub_default
Publication testpub_default
@@ -234,6 +237,7 @@ Indexes:
Publications:
"testpib_ins_trunct"
"testpub_fortbl"
+Parallel DML: unsafe
-- permissions
SET ROLE regress_publication_user2;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..0f8718f2a4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -171,6 +171,7 @@ Indexes:
"test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
"test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
Replica Identity: FULL
+Parallel DML: unsafe
ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 89397e41f0..396dc3a539 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -958,6 +958,7 @@ Policies:
Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
part_document_nonfiction FOR VALUES FROM (99) TO (100),
part_document_satire FOR VALUES FROM (55) TO (56)
+Parallel DML: unsafe
SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..cad4be0a8b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3155,6 +3155,7 @@ Rules:
r3 AS
ON DELETE TO rules_src DO
NOTIFY rules_src_deletion
+Parallel DML: unsafe
--
-- Ensure an aliased target relation for insert is correctly deparsed.
@@ -3183,6 +3184,7 @@ Rules:
r5 AS
ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text
WHERE trgt.f1 = new.f1
+Parallel DML: unsafe
--
-- Also check multiassignment deparsing.
@@ -3206,6 +3208,7 @@ Rules:
WHERE trgt.f1 = new.f1
RETURNING new.f1,
new.f2
+Parallel DML: unsafe
drop table rule_t1, rule_dest;
--
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c214d8dfc..c94eb3293e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -145,6 +145,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
b | integer | | | | plain | |
Statistics objects:
"public"."ab1_a_b_stats" ON a, b FROM ab1
+Parallel DML: unsafe
-- partial analyze doesn't build stats either
ANALYZE ab1 (a);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index c809f88f54..3fcd8e10f3 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -753,6 +753,7 @@ create table part_def partition of range_parted default;
e | character varying | | | | extended | |
Partition of: range_parted DEFAULT
Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
+Parallel DML: unsafe
insert into range_parted values ('c', 9);
-- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..11a750b58d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -339,6 +339,7 @@ Indexes:
"part_a_idx" btree (a), tablespace "regress_tblspace"
Partitions: testschema.part1 FOR VALUES IN (1),
testschema.part2 FOR VALUES IN (2)
+Parallel DML: unsafe
\d testschema.part1
Table "testschema.part1"
@@ -358,6 +359,7 @@ Partition of: testschema.part FOR VALUES IN (1)
Partition constraint: ((a IS NOT NULL) AND (a = 1))
Indexes:
"part1_a_idx" btree (a), tablespace "regress_tblspace"
+Parallel DML: unsafe
\d testschema.part_a_idx
Partitioned index "testschema.part_a_idx"
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..b4565f78ab
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,345 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+begin;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+select pg_get_table_max_parallel_dml_hazard('names2');
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+select pg_get_table_max_parallel_dml_hazard('names4');
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+
+rollback;
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
On Monday, July 12, 2021 4:01 PM <houzj.fnst@fujitsu.com> wrote:
Attach a rebased patchset which fix some compile warnings and errors due to
recent commit 2ed532.
Attach rebased patches.
Best regards
Houzj
Attachments:
v13-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v13-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 8473f06e63dc9017f262c29078084234ac705389 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 20 Jul 2021 09:06:18 +0800
Subject: [PATCH] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 93 +++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 65 +++++++++----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 ++++++++--
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 69 ++++++++++++--
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 283 insertions(+), 39 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..88fcd57 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3..135df96 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -302,6 +302,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -404,7 +405,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -959,6 +961,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1152,6 +1155,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1299,6 +1303,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74..7a96ef7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 147b5ab..b32d2d4 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -251,6 +251,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e..2151121 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 0982851..7607b91 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc0..3847700 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 46b108c..17766e2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -601,6 +602,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -646,6 +648,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -924,6 +927,30 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot perform parallel data modification on relation \"%s\"",
+ relname),
+ errdetail_relkind_not_supported(relkind)));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -942,6 +969,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4184,6 +4212,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4716,6 +4745,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5118,6 +5152,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -6075,6 +6112,8 @@ alter_table_type_to_string(AlterTableType cmdtype)
return "ALTER COLUMN ... DROP IDENTITY";
case AT_ReAddStatistics:
return NULL; /* not real grammar */
+ case AT_ParallelDMLSafety:
+ return "PARALLEL DML SAFETY";
}
return NULL;
@@ -18713,3 +18752,57 @@ GetAttributeCompression(Oid atttypid, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot perform parallel data modification on relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 93eeff9..a2f06c3 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2525,6 +2525,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0..65f33a9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9d4893c..870ab02 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3534,6 +3534,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9cc7b1..2705b61 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1292,6 +1293,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e73be21..b6f4ae8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2714,6 +2715,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 77d082d..ba725cb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 10da5c5..2971bc4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -609,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2683,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3276,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3290,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3310,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3331,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3352,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3374,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3396,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4081,6 +4097,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4228,7 +4248,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4237,6 +4257,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5016,7 +5037,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5028,15 +5049,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5048,15 +5070,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5069,15 +5092,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5090,10 +5114,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15539,6 +15564,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16079,6 +16105,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994..0addeef 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1873,6 +1873,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3359,7 +3360,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3509,6 +3511,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 34b91bb..4625f75 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6278,6 +6278,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6383,7 +6384,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6475,7 +6476,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6528,7 +6529,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6581,7 +6582,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6634,7 +6635,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6685,7 +6686,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6733,7 +6734,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6781,7 +6782,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6828,7 +6829,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6897,6 +6898,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6952,6 +6954,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16580,6 +16583,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e..8175a0b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -270,6 +270,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ba658f7..c1217e0 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1656,6 +1656,7 @@ describeOneTableDetails(const char *schemaname,
char *reloftype;
char relpersistence;
char relreplident;
+ char relparalleldml;
char *relam;
} tableinfo;
bool show_column_details = false;
@@ -1669,7 +1670,25 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
- if (pset.sversion >= 120000)
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+ "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+ "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+ "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+ "c.relpersistence, c.relreplident, am.amname, c.relparalleldml\n"
+ "FROM pg_catalog.pg_class c\n "
+ "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+ "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+ "WHERE c.oid = '%s';",
+ (verbose ?
+ "pg_catalog.array_to_string(c.reloptions || "
+ "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+ : "''"),
+ oid);
+ }
+ else if (pset.sversion >= 120000)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1872,8 @@ describeOneTableDetails(const char *schemaname,
(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
else
tableinfo.relam = NULL;
+ tableinfo.relparalleldml = (pset.sversion >= 150000) ?
+ *(PQgetvalue(res, 0, 15)) : 0;
PQclear(res);
res = NULL;
@@ -3630,6 +3651,20 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
printTableAddFooter(&cont, buf.data);
}
+
+ if (verbose &&
+ (tableinfo.relkind == RELKIND_RELATION ||
+ tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+ tableinfo.relkind == RELKIND_FOREIGN_TABLE) &&
+ tableinfo.relparalleldml != 0)
+ {
+ printfPQExpBuffer(&buf, _("Parallel DML: %s"),
+ tableinfo.relparalleldml == 'u' ? "unsafe" :
+ tableinfo.relparalleldml == 'r' ? "restricted" :
+ tableinfo.relparalleldml == 's' ? "safe" :
+ "???");
+ printTableAddFooter(&cont, buf.data);
+ }
}
/* reloptions, if verbose */
@@ -4005,7 +4040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
PGresult *res;
printQueryOpt myopt = pset.popt;
int cols_so_far;
- bool translate_columns[] = {false, false, true, false, false, false, false, false, false};
+ bool translate_columns[] = {false, false, true, false, false, false, false, false, false, false};
/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
@@ -4073,22 +4108,42 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("unlogged"),
gettext_noop("Persistence"));
translate_columns[cols_so_far] = true;
+ cols_so_far++;
}
- /*
- * We don't bother to count cols_so_far below here, as there's no need
- * to; this might change with future additions to the output columns.
- */
-
/*
* Access methods exist for tables, materialized views and indexes.
* This has been introduced in PostgreSQL 12 for tables.
*/
if (pset.sversion >= 120000 && !pset.hide_tableam &&
(showTables || showMatViews || showIndexes))
+ {
appendPQExpBuffer(&buf,
",\n am.amname as \"%s\"",
gettext_noop("Access method"));
+ cols_so_far++;
+ }
+
+ /*
+ * Show whether the data in the relation is unsafe('u'),
+ * restricted('r'), or safe('s') can be modified in parallel mode.
+ * This has been introduced in PostgreSQL 15 for tables.
+ */
+ if (pset.sversion >= 150000)
+ {
+ appendPQExpBuffer(&buf,
+ ",\n CASE c.relparalleldml WHEN 'u' THEN '%s' WHEN 'r' THEN '%s' WHEN 's' THEN '%s' END as \"%s\"",
+ gettext_noop("unsafe"),
+ gettext_noop("restricted"),
+ gettext_noop("safe"),
+ gettext_noop("Parallel DML"));
+ translate_columns[cols_so_far] = true;
+ }
+
+ /*
+ * We don't bother to count cols_so_far below here, as there's no need
+ * to; this might change with future additions to the output columns.
+ */
/*
* As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..b599759 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945..4d37c6a 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651..f93679b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2179,6 +2180,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 996c3e4..1521373 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf..05222fa 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855..5ea225a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5..e1f5678 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.18.4
v13-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v13-0002-parallel-SELECT-for-INSERT.patchDownload
From 49f57ad7508e2ee33fade8a1a7358b7c4ec09eaa Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:32:54 +0800
Subject: [PATCH 2/3] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++++
src/backend/executor/execMain.c | 3 ++
src/backend/optimizer/plan/planner.c | 21 ++++-----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4414459..2d68e46 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1015,6 +1015,32 @@ IsInParallelMode(void)
}
/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
+/*
* CommandCounterIncrement
*/
void
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4ba..ea685f0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4e..7736813 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 517712a..7c58c88 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -148,6 +152,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -615,12 +620,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -854,6 +881,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f686..fd3f86b 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887..32b5656 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.7.2.windows.1
v13-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v13-0003-get-parallel-safety-functions.patchDownload
From eed49aee131413d0785d68712c70e06874399e83 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Tue, 6 Jul 2021 11:23:45 +0800
Subject: [PATCH] get-parallel-safety-functions
Provide a utility function "pg_get_table_parallel_dml_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_table_max_parallel_dml_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 655 ++++++++++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 +++++
src/backend/utils/cache/typcache.c | 17 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 795 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7c58c88..406f54f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all; /* whether collect all the unsafe/restricted objects */
+ List *objects; /* parallel unsafe/restricted objects */
+ PartitionDirectory partition_directory; /* partition descriptors */
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,24 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context,
+ bool is_partition);
+static bool target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context);
+static bool target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition);
+static bool target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -153,6 +182,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -626,6 +656,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -678,6 +711,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -709,7 +745,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -726,6 +762,82 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+/*
+ * make_safety_object
+ *
+ * Creates an safety_object given object id, class id and parallel safety.
+ */
+static safety_object *
+make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+/* check_functions_in_node callback */
+static bool
+parallel_hazard_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object = make_safety_object(func_id,
+ ProcedureRelationId,
+ proparallel);
+ cont->objects = lappend(cont->objects, object);
+ }
+
+ return false;
+}
+
+/*
+ * parallel_hazard_walker
+ *
+ * Recursively search an expression tree which is defined as partition key or
+ * index or constraint or column default expression for PARALLEL
+ * UNSAFE/RESTRICTED table-related objects.
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_hazard_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_parallel_hazard(domain->resulttype, context))
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_hazard_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -882,6 +994,547 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
+ * target_rel_parallel_hazard
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+List*
+target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.objects = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_parallel_hazard_recurse(targetRel, &context, false);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.objects;
+}
+
+/*
+ * target_rel_parallel_hazard_recurse
+ *
+ * Recursively search all table-related objects for PARALLEL UNSAFE/RESTRICTED
+ * objects.
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_parallel_hazard_recurse(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ {
+ safety_object *object = make_safety_object(rel->rd_rel->oid,
+ RelationRelationId,
+ PROPARALLEL_RESTRICTED);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_parallel_hazard(rel, context, is_partition))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef && !is_partition)
+ {
+ Node *defaultexpr;
+
+ defaultexpr = build_column_default(rel, attnum + 1);
+ if (parallel_hazard_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified relation's trigger data.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object,
+ *parent_object;
+
+ object = make_safety_object(tgfoid, ProcedureRelationId,
+ proparallel);
+ parent_object = make_safety_object(tgoid, TriggerRelationId,
+ proparallel);
+
+ context->objects = lappend(context->objects, object);
+ context->objects = lappend(context->objects, parent_object);
+ }
+ }
+
+ return false;
+}
+
+/*
+ * index_expr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the input index expression and index predicate.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ Form_pg_index indexStruct;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ /* Check parallel-safety of index expression */
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_hazard_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+
+ /* Check parallel-safety of index predicate */
+ if (parallel_hazard_walker((Node *) ii_Predicate, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_index_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any existing index expressions or index predicate of a specified
+ * relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ char temp_hazard;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_parallel_hazard(index_rel,
+ ii_Expressions,
+ ii_Predicate,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+
+ object = make_safety_object(index_oid, IndexRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified DOMAIN type. Only any CHECK expressions are
+ * examined for parallel-safety.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+ char temp_hazard;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) r->check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+ Oid constr_oid = get_domain_constraint_oid(typid,
+ r->name,
+ false);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any partitions of a specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs,
+ *qual;
+
+ /*
+ * The partition check expression is composed of its parent table's
+ * partition key expression, we do not need to check it again for a
+ * partition because we already checked the parallel safety of its parent
+ * table's partition key expression.
+ */
+ if (!is_partition)
+ {
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_hazard_walker((Node *) qual, context))
+ return true;
+ }
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object;
+
+ object = make_safety_object(funcOid, ProcedureRelationId,
+ proparallel);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_hazard_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_parallel_hazard_recurse(part_rel,
+ context,
+ true);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any CHECK expressions or CHECK constraints related to the
+ * specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ char temp_hazard;
+ int i;
+ TupleDesc tupdesc;
+ List *temp_objects;
+ ConstrCheck *check;
+
+ tupdesc = RelationGetDescr(rel);
+
+ if (tupdesc->constr == NULL)
+ return false;
+
+ check = tupdesc->constr->check;
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ if (context->objects != NIL)
+ {
+ Oid constr_oid;
+ safety_object *object;
+
+ constr_oid = get_relation_constraint_oid(rel->rd_rel->oid,
+ check->ccname,
+ true);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+}
+
+/*
* is_parallel_allowed_for_modify
*
* Check at a high-level if parallel mode is able to be used for the specified
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4d..06d859c 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_table_max_parallel_dml_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_table_parallel_dml_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index de96e96..236c979 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2519,6 +2519,23 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
}
/*
+ * GetDomainConstraints --- get DomainConstraintState list of specified domain type
+ */
+List *
+GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
+/*
* Load (or re-load) the enumData member of the typcache entry.
*/
static void
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4..274cd4f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3766,6 +3766,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_table_parallel_dml_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_table_parallel_dml_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_table_max_parallel_dml_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_table_max_parallel_dml_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3773,11 +3787,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b5656..67e8f50 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a..28ca7d8 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.7.2.windows.1
v13-0004-regression-test-and-doc-updates.patchapplication/octet-stream; name=v13-0004-regression-test-and-doc-updates.patchDownload
From 2f2b6e0204a24b013ab0c5978dd907ebca95bfff Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 6 Jul 2021 11:02:39 +0800
Subject: [PATCH] regression-test-and-doc-updates
---
contrib/test_decoding/expected/ddl.out | 4 +
doc/src/sgml/func.sgml | 61 ++
doc/src/sgml/ref/alter_foreign_table.sgml | 13 +
doc/src/sgml/ref/alter_table.sgml | 12 +
doc/src/sgml/ref/create_foreign_table.sgml | 37 ++
doc/src/sgml/ref/create_table.sgml | 38 ++
doc/src/sgml/ref/create_table_as.sgml | 23 +
src/test/regress/expected/alter_table.out | 2 +
src/test/regress/expected/compression_1.out | 9 +
src/test/regress/expected/copy2.out | 1 +
src/test/regress/expected/create_table.out | 14 +
.../regress/expected/create_table_like.out | 8 +
src/test/regress/expected/domain.out | 2 +
src/test/regress/expected/foreign_data.out | 42 ++
src/test/regress/expected/identity.out | 1 +
src/test/regress/expected/inherit.out | 13 +
src/test/regress/expected/insert.out | 12 +
src/test/regress/expected/insert_parallel.out | 588 ++++++++++++++++++
src/test/regress/expected/psql.out | 58 +-
src/test/regress/expected/publication.out | 4 +
.../regress/expected/replica_identity.out | 1 +
src/test/regress/expected/rowsecurity.out | 1 +
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats_ext.out | 1 +
src/test/regress/expected/update.out | 1 +
src/test/regress/output/tablespace.source | 2 +
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 345 ++++++++++
28 files changed, 1270 insertions(+), 27 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 4ff0044c78..1d7eebb897 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -446,6 +446,7 @@ WITH (user_catalog_table = true)
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -460,6 +461,7 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
INSERT INTO replication_metadata(relation, options)
VALUES ('bar', ARRAY['a', 'b']);
@@ -473,6 +475,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -492,6 +495,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
rewritemeornot | integer | | | | plain | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=false
INSERT INTO replication_metadata(relation, options)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6388385edc..b496332217 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23909,6 +23909,67 @@ SELECT collation for ('foo' COLLATE "de_DE");
Undefined objects are identified with <literal>NULL</literal> values.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_table_parallel_dml_safety</primary>
+ </indexterm>
+ <function>pg_get_table_parallel_dml_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+ <returnvalue>record</returnvalue>
+ ( <parameter>objid</parameter> <type>oid</type>,
+ <parameter>classid</parameter> <type>oid</type>,
+ <parameter>proparallel</parameter> <type>char</type> )
+ </para>
+ <para>
+ Returns a row containing enough information to uniquely identify the
+ parallel unsafe/restricted table-related objects from which the
+ table's parallel DML safety is determined. The user can use this
+ information during development in order to accurately declare a
+ table's parallel DML safety, or to identify any problematic objects
+ if parallel DML fails or behaves unexpectedly. Note that when the
+ use of an object-related parallel unsafe/restricted function is
+ detected, both the function OID and the object OID are returned.
+ <parameter>classid</parameter> is the OID of the system catalog
+ containing the object;
+ <parameter>objid</parameter> is the OID of the object itself.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_table_max_parallel_dml_hazard</primary>
+ </indexterm>
+ <function>pg_get_table_max_parallel_dml_hazard</function> ( <type>regclass</type> )
+ <returnvalue>char</returnvalue>
+ </para>
+ <para>
+ Returns the worst parallel DML safety hazard that can be found in the
+ given relation:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>s</literal> safe
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>r</literal> restricted
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>u</literal> unsafe
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ Users can use this function to do a quick check without caring about
+ specific parallel-related objects.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 7ca03f3ac9..c1652e8312 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -29,6 +29,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -299,6 +301,17 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See the similar form of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>
+ for more details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c5e5e84e06..963c8cfcc6 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -37,6 +37,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -1010,6 +1012,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See <link linkend="sql-createtable"><command>CREATE TABLE</command></link> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477efe58..fbd5371eca 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -290,6 +292,41 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table (e.g., functions in triggers/index expression/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_parallel_dml_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">server_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..d6db677e3a 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
@@ -1336,6 +1339,41 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-paralleldmlsafety">
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table
+ (e.g., functions in triggers/index expressions/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_parallel_dml_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab56c..71c932b048 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
AS <replaceable>query</replaceable>
[ WITH [ NO ] DATA ]
</synopsis>
@@ -223,6 +224,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>query</replaceable></term>
<listitem>
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f81bdf513b..e800a218b5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2203,6 +2203,7 @@ alter table test_storage alter column a set storage external;
b | integer | | | 0 | plain | |
Indexes:
"test_storage_idx" btree (b, a)
+Parallel DML: unsafe
\d+ test_storage_idx
Index "public.test_storage_idx"
@@ -4190,6 +4191,7 @@ ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
a | integer | | | | plain | |
Partition key: RANGE (a)
Number of partitions: 0
+Parallel DML: unsafe
-- constraint should be created
\d part_rp
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index 1ce2962d55..041e829b37 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -12,6 +12,7 @@ INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
ERROR: compression method lz4 not supported
@@ -51,6 +52,7 @@ SELECT * INTO cmmove1 FROM cmdata;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | text | | | | extended | | |
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmmove1;
pg_column_compression
@@ -138,6 +140,7 @@ CREATE TABLE cmdata2 (f1 int);
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
\d+ cmdata2
@@ -145,6 +148,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
\d+ cmdata2
@@ -152,6 +156,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
--changing column storage should not impact the compression method
--but the data should not be compressed
@@ -162,6 +167,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | pglz | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
\d+ cmdata2
@@ -169,6 +175,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | pglz | |
+Parallel DML: unsafe
INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
SELECT pg_column_compression(f1) FROM cmdata2;
@@ -249,6 +256,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmdata;
pg_column_compression
@@ -263,6 +271,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | | |
+Parallel DML: unsafe
-- test alter compression method for materialized views
ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c64f0719e7..df20bcb97b 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -517,6 +517,7 @@ alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
f1 | integer | | | | plain | |
Check constraints:
"check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+Parallel DML: unsafe
copy check_con_tbl from stdin;
NOTICE: input = {"f1":1}
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 96bf426d98..13e2c1ca66 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,6 +505,7 @@ Number of partitions: 0
b | text | | | | extended | |
Partition key: RANGE (((a + 1)), substr(b, 1, 5))
Number of partitions: 0
+Parallel DML: unsafe
INSERT INTO partitioned2 VALUES (1, 'hello');
ERROR: no partition of relation "partitioned2" found for row
@@ -518,6 +519,7 @@ CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO
b | text | | | | extended | |
Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+Parallel DML: unsafe
DROP TABLE partitioned, partitioned2;
-- check reference to partitioned table's rowtype in partition descriptor
@@ -559,6 +561,7 @@ select * from partitioned where partitioned = '(1,2)'::partitioned;
b | integer | | | | plain | |
Partition of: partitioned FOR VALUES IN ('(1,2)')
Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+Parallel DML: unsafe
drop table partitioned;
-- check that dependencies of partition columns are handled correctly
@@ -618,6 +621,7 @@ Partitions: part_null FOR VALUES IN (NULL),
part_p1 FOR VALUES IN (1),
part_p2 FOR VALUES IN (2),
part_p3 FOR VALUES IN (3)
+Parallel DML: unsafe
-- forbidden expressions for partition bound with list partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
@@ -1064,6 +1068,7 @@ drop table test_part_coll_posix;
b | integer | | not null | 1 | plain | |
Partition of: parted FOR VALUES IN ('b')
Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Parallel DML: unsafe
-- Both partition bound and partition key in describe output
\d+ part_c
@@ -1076,6 +1081,7 @@ Partition of: parted FOR VALUES IN ('c')
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
Partition key: RANGE (b)
Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+Parallel DML: unsafe
-- a level-2 partition's constraint will include the parent's expressions
\d+ part_c_1_10
@@ -1086,6 +1092,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
b | integer | | not null | 0 | plain | |
Partition of: part_c FOR VALUES FROM (1) TO (10)
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Parallel DML: unsafe
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
@@ -1120,6 +1127,7 @@ CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MI
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+Parallel DML: unsafe
DROP TABLE unbounded_range_part;
CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
@@ -1132,6 +1140,7 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALU
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Parallel DML: unsafe
CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
\d+ range_parted4_2
@@ -1143,6 +1152,7 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Parallel DML: unsafe
CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
\d+ range_parted4_3
@@ -1154,6 +1164,7 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Parallel DML: unsafe
DROP TABLE range_parted4;
-- user-defined operator class in partition key
@@ -1190,6 +1201,7 @@ SELECT obj_description('parted_col_comment'::regclass);
b | text | | | | extended | |
Partition key: LIST (a)
Number of partitions: 0
+Parallel DML: unsafe
DROP TABLE parted_col_comment;
-- list partitioning on array type column
@@ -1202,6 +1214,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
a | integer[] | | | | extended | |
Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+Parallel DML: unsafe
DROP TABLE arrlp;
-- partition on boolean column
@@ -1216,6 +1229,7 @@ create table boolspart_f partition of boolspart for values in (false);
Partition key: LIST (a)
Partitions: boolspart_f FOR VALUES IN (false),
boolspart_t FOR VALUES IN (true)
+Parallel DML: unsafe
drop table boolspart;
-- partitions mixing temporary and permanent relations
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 4dc5e6aa5f..af8de78bdd 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -333,6 +333,7 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
a | text | | not null | | main | |
b | text | | | | extended | |
c | text | | | | external | |
+Parallel DML: unsafe
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@@ -342,6 +343,7 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
a | text | | not null | | extended | | A
b | text | | | | extended | | B
c | text | | | | extended | | C
+Parallel DML: unsafe
CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -356,6 +358,7 @@ NOTICE: merging constraint "ctlt1_a_check" with inherited definition
Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
description
@@ -378,6 +381,7 @@ Check constraints:
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1,
ctlt3
+Parallel DML: unsafe
CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -395,6 +399,7 @@ Check constraints:
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
description
@@ -418,6 +423,7 @@ Check constraints:
Statistics objects:
"public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
"public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
+Parallel DML: unsafe
SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
relname | objsubid | description
@@ -458,6 +464,7 @@ Check constraints:
Statistics objects:
"public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
"public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
+Parallel DML: unsafe
DROP TABLE public.pg_attrdef;
-- Check that LIKE isn't confused when new table masks the old, either
@@ -480,6 +487,7 @@ Check constraints:
Statistics objects:
"ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
"ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
+Parallel DML: unsafe
ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..2419d96a33 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -276,6 +276,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
WHERE (dcomptable.d1).i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
@@ -413,6 +414,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
WHERE dcomptable.d1[1].i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 5385f98a0f..4f50410f39 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -731,6 +731,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
\det+
List of foreign tables
@@ -852,6 +853,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- can't change the column type if it's used elsewhere
CREATE TABLE use_ft1_column_type (x ft1);
@@ -1390,6 +1392,7 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1401,6 +1404,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2;
\d+ fd_pt1
@@ -1410,6 +1414,7 @@ DROP FOREIGN TABLE ft2;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Parallel DML: unsafe
CREATE FOREIGN TABLE ft2 (
c1 integer NOT NULL,
@@ -1425,6 +1430,7 @@ CREATE FOREIGN TABLE ft2 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
\d+ fd_pt1
@@ -1435,6 +1441,7 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1446,6 +1453,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
CREATE TABLE ct3() INHERITS(ft2);
CREATE FOREIGN TABLE ft3 (
@@ -1469,6 +1477,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1478,6 +1487,7 @@ Child tables: ct3,
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1488,6 +1498,7 @@ Inherits: ft2
c3 | date | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- add attributes recursively
ALTER TABLE fd_pt1 ADD COLUMN c4 integer;
@@ -1508,6 +1519,7 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1526,6 +1538,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1540,6 +1553,7 @@ Child tables: ct3,
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1555,6 +1569,7 @@ Inherits: ft2
c8 | integer | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- alter attributes recursively
ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0;
@@ -1582,6 +1597,7 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
c7 | integer | | | | plain | |
c8 | text | | | | external | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1600,6 +1616,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- drop attributes recursively
ALTER TABLE fd_pt1 DROP COLUMN c4;
@@ -1615,6 +1632,7 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1628,6 +1646,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- add constraints recursively
ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT;
@@ -1655,6 +1674,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1670,6 +1690,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2; -- ERROR
ERROR: cannot drop foreign table ft2 because other objects depend on it
@@ -1702,6 +1723,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1715,6 +1737,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- drop constraints recursively
ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE;
@@ -1732,6 +1755,7 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1746,6 +1770,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- VALIDATE CONSTRAINT need do nothing on foreign tables
ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
@@ -1759,6 +1784,7 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1773,6 +1799,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- changes name of an attribute recursively
ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1;
@@ -1790,6 +1817,7 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1804,6 +1832,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR
@@ -1853,6 +1882,7 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1865,6 +1895,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- partition cannot have additional columns
DROP FOREIGN TABLE fd_pt2_1;
@@ -1884,6 +1915,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c4 | character(1) | | | | | extended | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
@@ -1898,6 +1930,7 @@ DROP FOREIGN TABLE fd_pt2_1;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
CREATE FOREIGN TABLE fd_pt2_1 (
c1 integer NOT NULL,
@@ -1913,6 +1946,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- no attach partition validation occurs for foreign tables
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
@@ -1925,6 +1959,7 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1937,6 +1972,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot add column to a partition
ALTER TABLE fd_pt2_1 ADD c4 char;
@@ -1953,6 +1989,7 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1967,6 +2004,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot drop inherited NOT NULL constraint from a partition
ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
@@ -1983,6 +2021,7 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1995,6 +2034,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: column "c2" in child table must be marked NOT NULL
@@ -2013,6 +2053,7 @@ Partition key: LIST (c1)
Check constraints:
"fd_pt2chk1" CHECK (c1 > 0)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -2025,6 +2066,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: child table is missing constraint "fd_pt2chk1"
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7..da24c16d09 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -506,6 +506,7 @@ TABLE itest8;
f3 | integer | | not null | generated by default as identity | plain | |
f4 | bigint | | not null | generated always as identity | plain | |
f5 | bigint | | | | plain | |
+Parallel DML: unsafe
\d itest8_f2_seq
Sequence "public.itest8_f2_seq"
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 06f44287bc..33a216ea4d 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1059,6 +1059,7 @@ ALTER TABLE inhts RENAME d TO dd;
dd | integer | | | | plain | |
Inherits: inht1,
inhs1
+Parallel DML: unsafe
DROP TABLE inhts;
-- Test for renaming in diamond inheritance
@@ -1079,6 +1080,7 @@ ALTER TABLE inht1 RENAME aa TO aaa;
z | integer | | | | plain | |
Inherits: inht2,
inht3
+Parallel DML: unsafe
CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
NOTICE: merging multiple inherited definitions of column "b"
@@ -1096,6 +1098,7 @@ ERROR: cannot rename inherited column "b"
d | integer | | | | plain | |
Inherits: inht2,
inhs1
+Parallel DML: unsafe
WITH RECURSIVE r AS (
SELECT 'inht1'::regclass AS inhrelid
@@ -1142,6 +1145,7 @@ CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
Indexes:
"test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
Child tables: test_constraints_inh
+Parallel DML: unsafe
ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
\d+ test_constraints
@@ -1152,6 +1156,7 @@ ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Child tables: test_constraints_inh
+Parallel DML: unsafe
\d+ test_constraints_inh
Table "public.test_constraints_inh"
@@ -1161,6 +1166,7 @@ Child tables: test_constraints_inh
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Inherits: test_constraints
+Parallel DML: unsafe
DROP TABLE test_constraints_inh;
DROP TABLE test_constraints;
@@ -1177,6 +1183,7 @@ CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
Indexes:
"test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
\d+ test_ex_constraints
@@ -1185,6 +1192,7 @@ ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
\d+ test_ex_constraints_inh
Table "public.test_ex_constraints_inh"
@@ -1192,6 +1200,7 @@ Child tables: test_ex_constraints_inh
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Inherits: test_ex_constraints
+Parallel DML: unsafe
DROP TABLE test_ex_constraints_inh;
DROP TABLE test_ex_constraints;
@@ -1208,6 +1217,7 @@ Indexes:
"test_primary_constraints_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Parallel DML: unsafe
\d+ test_foreign_constraints
Table "public.test_foreign_constraints"
@@ -1217,6 +1227,7 @@ Referenced by:
Foreign-key constraints:
"test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
\d+ test_foreign_constraints
@@ -1225,6 +1236,7 @@ ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
\d+ test_foreign_constraints_inh
Table "public.test_foreign_constraints_inh"
@@ -1232,6 +1244,7 @@ Child tables: test_foreign_constraints_inh
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Inherits: test_foreign_constraints
+Parallel DML: unsafe
DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..cb8cd958aa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -177,6 +177,7 @@ Rules:
irule3 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
new.f2
+Parallel DML: unsafe
drop table inserttest2;
drop table inserttest;
@@ -482,6 +483,7 @@ Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
part_null FOR VALUES IN (NULL),
part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
part_default DEFAULT, PARTITIONED
+Parallel DML: unsafe
-- cleanup
drop table range_parted, list_parted;
@@ -497,6 +499,7 @@ create table part_default partition of list_parted default;
a | integer | | | | plain | |
Partition of: list_parted DEFAULT
No partition constraint
+Parallel DML: unsafe
insert into part_default values (null);
insert into part_default values (1);
@@ -888,6 +891,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Parallel DML: unsafe
\d+ mcrparted1_lt_b
Table "public.mcrparted1_lt_b"
@@ -897,6 +901,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+Parallel DML: unsafe
\d+ mcrparted2_b
Table "public.mcrparted2_b"
@@ -906,6 +911,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+Parallel DML: unsafe
\d+ mcrparted3_c_to_common
Table "public.mcrparted3_c_to_common"
@@ -915,6 +921,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+Parallel DML: unsafe
\d+ mcrparted4_common_lt_0
Table "public.mcrparted4_common_lt_0"
@@ -924,6 +931,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+Parallel DML: unsafe
\d+ mcrparted5_common_0_to_10
Table "public.mcrparted5_common_0_to_10"
@@ -933,6 +941,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+Parallel DML: unsafe
\d+ mcrparted6_common_ge_10
Table "public.mcrparted6_common_ge_10"
@@ -942,6 +951,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+Parallel DML: unsafe
\d+ mcrparted7_gt_common_lt_d
Table "public.mcrparted7_gt_common_lt_d"
@@ -951,6 +961,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+Parallel DML: unsafe
\d+ mcrparted8_ge_d
Table "public.mcrparted8_ge_d"
@@ -960,6 +971,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+Parallel DML: unsafe
insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
('comm', -10), ('common', -10), ('common', 0), ('common', 10),
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..f5d22ce649
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,588 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+--
+-- END: setup some tables and data needed by the tests.
+--
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+--------------------------
+ Insert on para_insert_p1
+ -> Seq Scan on tenk1
+(2 rows)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names2');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names4');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+----------------------------------------
+ Insert on names5
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+rollback;
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1901673622 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2818,6 +2818,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2825,6 +2826,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\set HIDE_TABLEAM off
\d+ tbl_heap_psql
@@ -2834,6 +2836,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap_psql
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2842,50 +2845,51 @@ Access method: heap_psql
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap
+Parallel DML: unsafe
-- AM is displayed for tables, indexes and materialized views.
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | | unsafe | 0 bytes |
(4 rows)
\dt+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+---------------+-------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(2 rows)
\dm+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(1 row)
-- But not for views and sequences.
\dv+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+----------------+------+----------------------+-------------+---------+-------------
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+----------------+------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(1 row)
\set HIDE_TABLEAM on
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(4 rows)
RESET ROLE;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 63d6ab7a4e..314ec05dc1 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -83,6 +83,7 @@ Indexes:
"testpub_tbl2_pkey" PRIMARY KEY, btree (id)
Publications:
"testpub_foralltables"
+Parallel DML: unsafe
\dRp+ testpub_foralltables
Publication testpub_foralltables
@@ -196,6 +197,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\d+ testpub_tbl1
Table "public.testpub_tbl1"
@@ -209,6 +211,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\dRp+ testpub_default
Publication testpub_default
@@ -234,6 +237,7 @@ Indexes:
Publications:
"testpib_ins_trunct"
"testpub_fortbl"
+Parallel DML: unsafe
-- permissions
SET ROLE regress_publication_user2;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..0f8718f2a4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -171,6 +171,7 @@ Indexes:
"test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
"test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
Replica Identity: FULL
+Parallel DML: unsafe
ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 89397e41f0..396dc3a539 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -958,6 +958,7 @@ Policies:
Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
part_document_nonfiction FOR VALUES FROM (99) TO (100),
part_document_satire FOR VALUES FROM (55) TO (56)
+Parallel DML: unsafe
SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..cad4be0a8b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3155,6 +3155,7 @@ Rules:
r3 AS
ON DELETE TO rules_src DO
NOTIFY rules_src_deletion
+Parallel DML: unsafe
--
-- Ensure an aliased target relation for insert is correctly deparsed.
@@ -3183,6 +3184,7 @@ Rules:
r5 AS
ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text
WHERE trgt.f1 = new.f1
+Parallel DML: unsafe
--
-- Also check multiassignment deparsing.
@@ -3206,6 +3208,7 @@ Rules:
WHERE trgt.f1 = new.f1
RETURNING new.f1,
new.f2
+Parallel DML: unsafe
drop table rule_t1, rule_dest;
--
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 8c214d8dfc..c94eb3293e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -145,6 +145,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
b | integer | | | | plain | |
Statistics objects:
"public"."ab1_a_b_stats" ON a, b FROM ab1
+Parallel DML: unsafe
-- partial analyze doesn't build stats either
ANALYZE ab1 (a);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index c809f88f54..3fcd8e10f3 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -753,6 +753,7 @@ create table part_def partition of range_parted default;
e | character varying | | | | extended | |
Partition of: range_parted DEFAULT
Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
+Parallel DML: unsafe
insert into range_parted values ('c', 9);
-- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..11a750b58d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -339,6 +339,7 @@ Indexes:
"part_a_idx" btree (a), tablespace "regress_tblspace"
Partitions: testschema.part1 FOR VALUES IN (1),
testschema.part2 FOR VALUES IN (2)
+Parallel DML: unsafe
\d testschema.part1
Table "testschema.part1"
@@ -358,6 +359,7 @@ Partition of: testschema.part FOR VALUES IN (1)
Partition constraint: ((a IS NOT NULL) AND (a = 1))
Indexes:
"part1_a_idx" btree (a), tablespace "regress_tblspace"
+Parallel DML: unsafe
\d testschema.part_a_idx
Partitioned index "testschema.part_a_idx"
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 22b0d3584d..46fa6b7e6b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..b4565f78ab
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,345 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+begin;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+select pg_get_table_max_parallel_dml_hazard('names2');
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+select pg_get_table_max_parallel_dml_hazard('names4');
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+
+rollback;
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.18.4
On Tue, Jul 20, 2021 at 11:47 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Attach rebased patches.
Just letting you know that CRLFs are in the patch comments for the
0001 and 0003 patches.
(It doesn't affect patch application)
Regards,
Greg Nancarrow
Fujitsu Australia
On Tue, Jul 20, 2021 at 11:47 AM houzj.fnst@fujitsu.com
<houzj.fnst@fujitsu.com> wrote:
Attach rebased patches.
There's a failure in the "triggers" tests and the cfbot is not happy.
Attaching an updated set of patches with a minor update to the
expected test results to fix this.
Also removed some CRLFs in some of the patch comments. No other changes.
Regards,
Greg Nancarrow
Fujitsu Australia
Attachments:
v14-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchapplication/octet-stream; name=v14-0001-CREATE-ALTER-TABLE-PARALLEL-DML.patchDownload
From 117e9aa6ff27cde6da0feed3e0daf804ba9af7e1 Mon Sep 17 00:00:00 2001
From: "houzj.fnst" <houzj.fnst@cn.fujitsu.com>
Date: Tue, 20 Jul 2021 09:06:18 +0800
Subject: [PATCH v14 1/4] CREATE-ALTER-TABLE-PARALLEL-DML
Enable users to declare a table's parallel data-modification safety
(SAFE/RESTRICTED/UNSAFE).
Add a table property that represents parallel safety of a table for
DML statement execution.
It may be specified as follows:
CREATE TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
ALTER TABLE table_name PARALLEL DML { UNSAFE | RESTRICTED | SAFE };
This property is recorded in pg_class's relparallel column as 'u',
'r', or 's', just like pg_proc's proparallel.
The default is UNSAFE.
The planner assumes that all of the table, its descendant partitions,
and their ancillary objects have, at worst, the specified parallel
safety. The user is responsible for its correctness.
---
src/backend/bootstrap/bootparse.y | 3 +
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 2 +
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 93 +++++++++++++++++++
src/backend/commands/typecmds.c | 1 +
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/parser/gram.y | 65 +++++++++----
src/backend/utils/cache/relcache.c | 6 +-
src/bin/pg_dump/pg_dump.c | 47 ++++++++--
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 69 ++++++++++++--
src/include/catalog/heap.h | 2 +
src/include/catalog/pg_class.h | 3 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
src/include/utils/relcache.h | 3 +-
.../test_ddl_deparse/test_ddl_deparse.c | 3 +
26 files changed, 283 insertions(+), 39 deletions(-)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..88fcd57082 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -25,6 +25,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
@@ -208,6 +209,7 @@ Boot_CreateStmt:
tupdesc,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
true,
@@ -231,6 +233,7 @@ Boot_CreateStmt:
NIL,
RELKIND_RELATION,
RELPERSISTENCE_PERMANENT,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..135df961c9 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -302,6 +302,7 @@ heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -404,7 +405,8 @@ heap_create(const char *relname,
shared_relation,
mapped_relation,
relpersistence,
- relkind);
+ relkind,
+ relparalleldml);
/*
* Have the storage manager create the relation's disk file, if needed.
@@ -959,6 +961,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relparalleldml - 1] = CharGetDatum(rd_rel->relparalleldml);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -1152,6 +1155,7 @@ heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
@@ -1299,6 +1303,7 @@ heap_create_with_catalog(const char *relname,
tupdesc,
relkind,
relpersistence,
+ relparalleldml,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce7..7a96ef760c 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -935,6 +936,7 @@ index_create(Relation heapRelation,
indexTupDesc,
relkind,
relpersistence,
+ PROPARALLEL_UNSAFE,
shared_relation,
mapped_relation,
allow_system_table_mods,
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 147b5abc19..b32d2d4132 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -251,6 +251,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
NIL,
RELKIND_TOASTVALUE,
rel->rd_rel->relpersistence,
+ rel->rd_rel->relparalleldml,
shared_relation,
mapped_relation,
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e3fc..2151121066 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -691,6 +691,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
NIL,
RELKIND_RELATION,
relpersistence,
+ OldHeap->rd_rel->relparalleldml,
false,
RelationIsMapped(OldHeap),
ONCOMMIT_NOOP,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 0982851715..7607b91ae8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -107,6 +107,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
+ create->paralleldmlsafety = into->paralleldmlsafety;
create->if_not_exists = false;
create->accessMethod = into->accessMethod;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 72bfdc07a4..384770050a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -211,6 +211,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->options = NIL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
+ stmt->paralleldmlsafety = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a16e749506..9fa9db5c13 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
@@ -601,6 +602,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, char *compression);
+static void ATExecParallelDMLSafety(Relation rel, Node *def);
/* ----------------------------------------------------------------
@@ -646,6 +648,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ char relparalleldml = PROPARALLEL_UNSAFE;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -924,6 +927,30 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
if (accessMethod != NULL)
accessMethodId = get_table_am_oid(accessMethod, false);
+ if (stmt->paralleldmlsafety != NULL)
+ {
+ if (strcmp(stmt->paralleldmlsafety, "safe") == 0)
+ {
+ if (relkind == RELKIND_FOREIGN_TABLE ||
+ stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot perform parallel data modification on relation \"%s\"",
+ relname),
+ errdetail_relkind_not_supported(relkind)));
+
+ relparalleldml = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(stmt->paralleldmlsafety, "restricted") == 0)
+ relparalleldml = PROPARALLEL_RESTRICTED;
+ else if (strcmp(stmt->paralleldmlsafety, "unsafe") == 0)
+ relparalleldml = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -942,6 +969,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
old_constraints),
relkind,
stmt->relation->relpersistence,
+ relparalleldml,
false,
false,
stmt->oncommit,
@@ -4184,6 +4212,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetIdentity:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_ParallelDMLSafety:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -4716,6 +4745,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_ParallelDMLSafety:
+ ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5118,6 +5152,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_ParallelDMLSafety:
+ ATExecParallelDMLSafety(rel, cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -6075,6 +6112,8 @@ alter_table_type_to_string(AlterTableType cmdtype)
return "ALTER COLUMN ... DROP IDENTITY";
case AT_ReAddStatistics:
return NULL; /* not real grammar */
+ case AT_ParallelDMLSafety:
+ return "PARALLEL DML SAFETY";
}
return NULL;
@@ -18713,3 +18752,57 @@ GetAttributeCompression(Oid atttypid, char *compression)
return cmethod;
}
+
+static void
+ATExecParallelDMLSafety(Relation rel, Node *def)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+ char relparallel = PROPARALLEL_SAFE;
+ char *parallel = strVal(def);
+
+ if (parallel)
+ {
+ if (strcmp(parallel, "safe") == 0)
+ {
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot perform parallel data modification on relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+
+ relparallel = PROPARALLEL_SAFE;
+ }
+ else if (strcmp(parallel, "restricted") == 0)
+ relparallel = PROPARALLEL_RESTRICTED;
+ else if (strcmp(parallel, "unsafe") == 0)
+ relparallel = PROPARALLEL_UNSAFE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("parameter \"parallel dml\" must be SAFE, RESTRICTED, or UNSAFE")));
+ }
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relparalleldml = relparallel;
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ table_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 93eeff950b..a2f06c3e79 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2525,6 +2525,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
createStmt->options = NIL;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33..65f33a95d8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -227,6 +227,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
createStmt->options = options;
createStmt->oncommit = ONCOMMIT_NOOP;
createStmt->tablespacename = NULL;
+ createStmt->paralleldmlsafety = NULL;
createStmt->if_not_exists = false;
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 29020c908e..df41165c5f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3534,6 +3534,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_STRING_FIELD(accessMethod);
+ COPY_STRING_FIELD(paralleldmlsafety);
COPY_SCALAR_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..67b1966f18 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -146,6 +146,7 @@ _equalIntoClause(const IntoClause *a, const IntoClause *b)
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_NODE_FIELD(viewQuery);
COMPARE_SCALAR_FIELD(skipData);
@@ -1292,6 +1293,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_STRING_FIELD(accessMethod);
+ COMPARE_STRING_FIELD(paralleldmlsafety);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 48202d2232..fdc5b63c28 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1107,6 +1107,7 @@ _outIntoClause(StringInfo str, const IntoClause *node)
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_NODE_FIELD(viewQuery);
WRITE_BOOL_FIELD(skipData);
}
@@ -2714,6 +2715,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_STRING_FIELD(accessMethod);
+ WRITE_STRING_FIELD(paralleldmlsafety);
WRITE_BOOL_FIELD(if_not_exists);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 77d082d8b4..ba725cb290 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -563,6 +563,7 @@ _readIntoClause(void)
READ_NODE_FIELD(options);
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
+ READ_STRING_FIELD(paralleldmlsafety);
READ_NODE_FIELD(viewQuery);
READ_BOOL_FIELD(skipData);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 10da5c5c51..2971bc41bb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -609,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <str> ParallelDMLSafety
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -654,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
- DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DML DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -2683,6 +2683,14 @@ alter_table_cmd:
n->subtype = AT_NoForceRowSecurity;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> PARALLEL DML SAFE/RESTRICTED/UNSAFE */
+ | PARALLEL DML ColId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ParallelDMLSafety;
+ n->def = (Node *)makeString($3);
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -3268,7 +3276,7 @@ copy_generic_opt_arg_list_item:
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptPartitionSpec table_access_method_clause OptWith
- OnCommitOption OptTableSpace
+ OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3282,12 +3290,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $11;
n->oncommit = $12;
n->tablespacename = $13;
+ n->paralleldmlsafety = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3301,12 +3310,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $14;
n->oncommit = $15;
n->tablespacename = $16;
+ n->paralleldmlsafety = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3321,12 +3331,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
+ n->paralleldmlsafety = $13;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3341,12 +3352,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
+ n->paralleldmlsafety = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -3361,12 +3374,14 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $12;
n->oncommit = $13;
n->tablespacename = $14;
+ n->paralleldmlsafety = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
table_access_method_clause OptWith OnCommitOption OptTableSpace
+ ParallelDMLSafety
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -3381,6 +3396,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->options = $15;
n->oncommit = $16;
n->tablespacename = $17;
+ n->paralleldmlsafety = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -4081,6 +4097,10 @@ OptTableSpace: TABLESPACE name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
;
+ParallelDMLSafety: PARALLEL DML name { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;
@@ -4228,7 +4248,7 @@ CreateAsStmt:
create_as_target:
qualified_name opt_column_list table_access_method_clause
- OptWith OnCommitOption OptTableSpace
+ OptWith OnCommitOption OptTableSpace ParallelDMLSafety
{
$$ = makeNode(IntoClause);
$$->rel = $1;
@@ -4237,6 +4257,7 @@ create_as_target:
$$->options = $4;
$$->onCommit = $5;
$$->tableSpaceName = $6;
+ $$->paralleldmlsafety = $7;
$$->viewQuery = NULL;
$$->skipData = false; /* might get changed later */
}
@@ -5016,7 +5037,7 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
CreateForeignTableStmt:
CREATE FOREIGN TABLE qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5028,15 +5049,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $9;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $10;
- n->options = $11;
+ n->servername = $11;
+ n->options = $12;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
'(' OptTableElementList ')'
- OptInherit SERVER name create_generic_options
+ OptInherit ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5048,15 +5070,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $12;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $13;
- n->options = $14;
+ n->servername = $14;
+ n->options = $15;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$4->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5069,15 +5092,16 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $10;
n->base.if_not_exists = false;
/* FDW-specific data */
- n->servername = $11;
- n->options = $12;
+ n->servername = $12;
+ n->options = $13;
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
- SERVER name create_generic_options
+ ParallelDMLSafety SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
$7->relpersistence = RELPERSISTENCE_PERMANENT;
@@ -5090,10 +5114,11 @@ CreateForeignTableStmt:
n->base.options = NIL;
n->base.oncommit = ONCOMMIT_NOOP;
n->base.tablespacename = NULL;
+ n->base.paralleldmlsafety = $13;
n->base.if_not_exists = true;
/* FDW-specific data */
- n->servername = $14;
- n->options = $15;
+ n->servername = $15;
+ n->options = $16;
$$ = (Node *) n;
}
;
@@ -15539,6 +15564,7 @@ unreserved_keyword:
| DICTIONARY
| DISABLE_P
| DISCARD
+ | DML
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
@@ -16079,6 +16105,7 @@ bare_label_keyword:
| DISABLE_P
| DISCARD
| DISTINCT
+ | DML
| DO
| DOCUMENT_P
| DOMAIN_P
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..0addeef390 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1873,6 +1873,7 @@ formrdesc(const char *relationName, Oid relationReltype,
relation->rd_rel->relkind = RELKIND_RELATION;
relation->rd_rel->relnatts = (int16) natts;
relation->rd_rel->relam = HEAP_TABLE_AM_OID;
+ relation->rd_rel->relparalleldml = PROPARALLEL_UNSAFE;
/*
* initialize attribute tuple form
@@ -3359,7 +3360,8 @@ RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind)
+ char relkind,
+ char relparalleldml)
{
Relation rel;
MemoryContext oldcxt;
@@ -3509,6 +3511,8 @@ RelationBuildLocalRelation(const char *relname,
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
+ rel->rd_rel->relparalleldml = relparalleldml;
+
/*
* Insert relation physical and logical identifiers (OIDs) into the right
* places. For a mapped relation, we set relfilenode to zero and rely on
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 34b91bb226..4625f757e2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6278,6 +6278,7 @@ getTables(Archive *fout, int *numTables)
int i_relpersistence;
int i_relispopulated;
int i_relreplident;
+ int i_relparalleldml;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
@@ -6383,7 +6384,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, am.amname, "
+ "c.relreplident, c.relparalleldml, c.relpages, am.amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -6475,7 +6476,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6528,7 +6529,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "c.relreplident, c.relpages, "
+ "c.relreplident, c.relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6581,7 +6582,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
"c.relpersistence, c.relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6634,7 +6635,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"c.relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"CASE WHEN c.relkind = 'f' THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
@@ -6685,7 +6686,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
@@ -6733,7 +6734,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6781,7 +6782,7 @@ getTables(Archive *fout, int *numTables)
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, c.relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, c.relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6828,7 +6829,7 @@ getTables(Archive *fout, int *numTables)
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
"'p' AS relpersistence, 't' as relispopulated, "
- "'d' AS relreplident, relpages, "
+ "'d' AS relreplident, 'u' AS relparalleldml, relpages, "
"NULL AS amname, "
"NULL AS foreignserver, "
"NULL AS reloftype, "
@@ -6897,6 +6898,7 @@ getTables(Archive *fout, int *numTables)
i_relpersistence = PQfnumber(res, "relpersistence");
i_relispopulated = PQfnumber(res, "relispopulated");
i_relreplident = PQfnumber(res, "relreplident");
+ i_relparalleldml = PQfnumber(res, "relparalleldml");
i_relpages = PQfnumber(res, "relpages");
i_foreignserver = PQfnumber(res, "foreignserver");
i_owning_tab = PQfnumber(res, "owning_tab");
@@ -6952,6 +6954,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relparalleldml = *(PQgetvalue(res, i, i_relparalleldml));
tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
@@ -16580,6 +16583,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
}
}
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ appendPQExpBuffer(q, "\nALTER %sTABLE %s PARALLEL DML ",
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "",
+ qualrelname);
+
+ switch (tbinfo->relparalleldml)
+ {
+ case 's':
+ appendPQExpBuffer(q, "SAFE;\n");
+ break;
+ case 'r':
+ appendPQExpBuffer(q, "RESTRICTED;\n");
+ break;
+ case 'u':
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ default:
+ /* should not reach here */
+ appendPQExpBuffer(q, "UNSAFE;\n");
+ break;
+ }
+ }
+
if (tbinfo->forcerowsec)
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
qualrelname);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f5e170e0db..8175a0bc82 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -270,6 +270,7 @@ typedef struct _tableInfo
char relpersistence; /* relation persistence */
bool relispopulated; /* relation is populated */
char relreplident; /* replica identifier */
+ char relparalleldml; /* parallel safety of dml on the relation */
char *reltablespace; /* relation tablespace */
char *reloptions; /* options specified by WITH (...) */
char *checkoption; /* WITH CHECK OPTION, if any */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ba658f731b..c1217e085b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1656,6 +1656,7 @@ describeOneTableDetails(const char *schemaname,
char *reloftype;
char relpersistence;
char relreplident;
+ char relparalleldml;
char *relam;
} tableinfo;
bool show_column_details = false;
@@ -1669,7 +1670,25 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
- if (pset.sversion >= 120000)
+ if (pset.sversion >= 150000)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+ "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
+ "false AS relhasoids, c.relispartition, %s, c.reltablespace, "
+ "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
+ "c.relpersistence, c.relreplident, am.amname, c.relparalleldml\n"
+ "FROM pg_catalog.pg_class c\n "
+ "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+ "LEFT JOIN pg_catalog.pg_am am ON (c.relam = am.oid)\n"
+ "WHERE c.oid = '%s';",
+ (verbose ?
+ "pg_catalog.array_to_string(c.reloptions || "
+ "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+ : "''"),
+ oid);
+ }
+ else if (pset.sversion >= 120000)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1853,6 +1872,8 @@ describeOneTableDetails(const char *schemaname,
(char *) NULL : pg_strdup(PQgetvalue(res, 0, 14));
else
tableinfo.relam = NULL;
+ tableinfo.relparalleldml = (pset.sversion >= 150000) ?
+ *(PQgetvalue(res, 0, 15)) : 0;
PQclear(res);
res = NULL;
@@ -3630,6 +3651,20 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&buf, _("Access method: %s"), tableinfo.relam);
printTableAddFooter(&cont, buf.data);
}
+
+ if (verbose &&
+ (tableinfo.relkind == RELKIND_RELATION ||
+ tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+ tableinfo.relkind == RELKIND_FOREIGN_TABLE) &&
+ tableinfo.relparalleldml != 0)
+ {
+ printfPQExpBuffer(&buf, _("Parallel DML: %s"),
+ tableinfo.relparalleldml == 'u' ? "unsafe" :
+ tableinfo.relparalleldml == 'r' ? "restricted" :
+ tableinfo.relparalleldml == 's' ? "safe" :
+ "???");
+ printTableAddFooter(&cont, buf.data);
+ }
}
/* reloptions, if verbose */
@@ -4005,7 +4040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
PGresult *res;
printQueryOpt myopt = pset.popt;
int cols_so_far;
- bool translate_columns[] = {false, false, true, false, false, false, false, false, false};
+ bool translate_columns[] = {false, false, true, false, false, false, false, false, false, false};
/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
@@ -4073,22 +4108,42 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("unlogged"),
gettext_noop("Persistence"));
translate_columns[cols_so_far] = true;
+ cols_so_far++;
}
- /*
- * We don't bother to count cols_so_far below here, as there's no need
- * to; this might change with future additions to the output columns.
- */
-
/*
* Access methods exist for tables, materialized views and indexes.
* This has been introduced in PostgreSQL 12 for tables.
*/
if (pset.sversion >= 120000 && !pset.hide_tableam &&
(showTables || showMatViews || showIndexes))
+ {
appendPQExpBuffer(&buf,
",\n am.amname as \"%s\"",
gettext_noop("Access method"));
+ cols_so_far++;
+ }
+
+ /*
+ * Show whether the data in the relation is unsafe('u'),
+ * restricted('r'), or safe('s') can be modified in parallel mode.
+ * This has been introduced in PostgreSQL 15 for tables.
+ */
+ if (pset.sversion >= 150000)
+ {
+ appendPQExpBuffer(&buf,
+ ",\n CASE c.relparalleldml WHEN 'u' THEN '%s' WHEN 'r' THEN '%s' WHEN 's' THEN '%s' END as \"%s\"",
+ gettext_noop("unsafe"),
+ gettext_noop("restricted"),
+ gettext_noop("safe"),
+ gettext_noop("Parallel DML"));
+ translate_columns[cols_so_far] = true;
+ }
+
+ /*
+ * We don't bother to count cols_so_far below here, as there's no need
+ * to; this might change with future additions to the output columns.
+ */
/*
* As of PostgreSQL 9.0, use pg_table_size() to show a more accurate
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b49c..b59975919b 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -55,6 +55,7 @@ extern Relation heap_create(const char *relname,
TupleDesc tupDesc,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
bool allow_system_table_mods,
@@ -73,6 +74,7 @@ extern Oid heap_create_with_catalog(const char *relname,
List *cooked_constraints,
char relkind,
char relpersistence,
+ char relparalleldml,
bool shared_relation,
bool mapped_relation,
OnCommitAction oncommit,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fef9945ed8..4d37c6accc 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -116,6 +116,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* see REPLICA_IDENTITY_xxx constants */
char relreplident BKI_DEFAULT(n);
+ /* parallel safety of the dml on the relation */
+ char relparalleldml BKI_DEFAULT(u);
+
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 947660a4b0..57bf8a45b9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1933,7 +1933,8 @@ typedef enum AlterTableType
AT_AddIdentity, /* ADD IDENTITY */
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
- AT_ReAddStatistics /* internal to commands/tablecmds.c */
+ AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_ParallelDMLSafety /* PARALLEL DML SAFE/RESTRICTED/UNSAFE */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -2179,6 +2180,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
char *accessMethod; /* table access method */
+ char *paralleldmlsafety; /* parallel dml safety */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index c04282f91f..6e679d9f97 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -115,6 +115,7 @@ typedef struct IntoClause
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ char *paralleldmlsafety; /* parallel dml safety */
Node *viewQuery; /* materialized view's SELECT query */
bool skipData; /* true for WITH NO DATA */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..05222faccd 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -139,6 +139,7 @@ PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dml", DML, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index f772855ac6..5ea225ac2d 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -108,7 +108,8 @@ extern Relation RelationBuildLocalRelation(const char *relname,
bool shared_relation,
bool mapped_relation,
char relpersistence,
- char relkind);
+ char relkind,
+ char relparalleldml);
/*
* Routines to manage assignment of new relfilenode to a relation
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 1bae1e5438..e1f5678eef 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -276,6 +276,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_NoForceRowSecurity:
strtype = "NO FORCE ROW SECURITY";
break;
+ case AT_ParallelDMLSafety:
+ strtype = "PARALLEL DML SAFETY";
+ break;
case AT_GenericOptions:
strtype = "SET OPTIONS";
break;
--
2.27.0
v14-0002-parallel-SELECT-for-INSERT.patchapplication/octet-stream; name=v14-0002-parallel-SELECT-for-INSERT.patchDownload
From 7cad3cf052856ec9f5e087f1edec1c24b920dc74 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@fujitsu.com>
Date: Mon, 31 May 2021 09:32:54 +0800
Subject: [PATCH v14 2/4] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++++
src/backend/executor/execMain.c | 3 +
src/backend/optimizer/plan/planner.c | 21 +++----
src/backend/optimizer/util/clauses.c | 87 +++++++++++++++++++++++++++-
src/include/access/xact.h | 15 +++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 441445927e..2d68e4633a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1014,6 +1014,32 @@ IsInParallelMode(void)
return CurrentTransactionState->parallelModeLevel != 0;
}
+/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
/*
* CommandCounterIncrement
*/
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3ce4bae53..ea685f0846 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1535,7 +1535,10 @@ ExecutePlan(EState *estate,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1868c4eff4..7736813230 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,16 +314,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
- * (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
- * MATERIALIZED VIEW to use parallel plans, but as of now, only the leader
- * backend writes into a completely new table. In the future, we can
- * extend it to allow workers to write into the table. However, to allow
- * parallel updates and deletes, we have to solve other problems,
- * especially around combo CIDs.)
+ * (Note that we do allow CREATE TABLE AS, INSERT INTO...SELECT, SELECT
+ * INTO, and CREATE MATERIALIZED VIEW to use parallel plans. However, as
+ * of now, only the leader backend writes into a completely new table. In
+ * the future, we can extend it to allow workers to write into the table.
+ * However, to allow parallel updates and deletes, we have to solve other
+ * problems, especially around combo CIDs.)
*
* For now, we don't try to use parallel mode if we're running inside a
* parallel worker. We might eventually be able to relax this
@@ -332,7 +332,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7187f17da5..ac0f243bf1 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -20,6 +20,8 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_language.h"
@@ -43,6 +45,7 @@
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -51,6 +54,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -151,6 +155,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
int nargs, List *args);
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
@@ -618,12 +623,34 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool max_hazard_found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ if (!max_hazard_found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -857,6 +884,64 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, we support only Inserts.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * (Note: we don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these)
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 134f6862da..fd3f86bf7c 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -466,5 +466,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887a85..32b56565e5 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -53,4 +53,6 @@ extern void CommuteOpExpr(OpExpr *clause);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.27.0
v14-0003-get-parallel-safety-functions.patchapplication/octet-stream; name=v14-0003-get-parallel-safety-functions.patchDownload
From f9af1af05ffdecea8d01aa0581c1180d69aecc31 Mon Sep 17 00:00:00 2001
From: houzj <houzj.fnst@cn.fujitsu.com>
Date: Tue, 6 Jul 2021 11:23:45 +0800
Subject: [PATCH v14 3/4] get-parallel-safety-functions
Provide a utility function "pg_get_table_parallel_dml_safety(regclass)" that
returns records of (objid, classid, parallel_safety) for all
parallel unsafe/restricted table-related objects from which the
table's parallel DML safety is determined. The user can use this
information during development in order to accurately declare a
table's parallel DML safety. Or to identify any problematic objects
if a parallel DML fails or behaves unexpectedly.
When the use of an index-related parallel unsafe/restricted function
is detected, both the function oid and the index oid are returned.
Provide a utility function "pg_get_table_max_parallel_dml_hazard(regclass)" that
returns the worst parallel DML safety hazard that can be found in the
given relation. Users can use this function to do a quick check without
caring about specific parallel-related objects.
---
src/backend/optimizer/util/clauses.c | 655 ++++++++++++++++++++++++++-
src/backend/utils/adt/misc.c | 94 ++++
src/backend/utils/cache/typcache.c | 17 +
src/include/catalog/pg_proc.dat | 22 +-
src/include/optimizer/clauses.h | 10 +
src/include/utils/typcache.h | 2 +
6 files changed, 795 insertions(+), 5 deletions(-)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index ac0f243bf1..960670bf82 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -19,15 +19,20 @@
#include "postgres.h"
+#include "access/amapi.h"
+#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
@@ -46,6 +51,8 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
@@ -54,6 +61,7 @@
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -92,6 +100,9 @@ typedef struct
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
+ bool check_all; /* whether collect all the unsafe/restricted objects */
+ List *objects; /* parallel unsafe/restricted objects */
+ PartitionDirectory partition_directory; /* partition descriptors */
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -102,6 +113,24 @@ static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool max_parallel_hazard_walker(Node *node,
max_parallel_hazard_context *context);
+static bool target_rel_parallel_hazard_recurse(Relation relation,
+ max_parallel_hazard_context *context,
+ bool is_partition);
+static bool target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context);
+static bool target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
+static bool target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context);
+static bool target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition);
+static bool target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_exec_param_walker(Node *node, List *param_ids);
static bool contain_context_dependent_node(Node *clause);
@@ -156,6 +185,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
static Node *substitute_actual_srf_parameters_mutator(Node *node,
substitute_actual_srf_parameters_context *context);
static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
+static safety_object *make_safety_object(Oid objid, Oid classid, char proparallel);
/*****************************************************************************
@@ -629,6 +659,9 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
max_hazard_found = max_parallel_hazard_walker((Node *) parse, &context);
@@ -681,6 +714,9 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_all = false;
+ context.objects = NIL;
+ context.partition_directory = NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -712,7 +748,7 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
- Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+ Assert(context->check_all || context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
@@ -729,6 +765,82 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
return false;
}
+/*
+ * make_safety_object
+ *
+ * Creates an safety_object given object id, class id and parallel safety.
+ */
+static safety_object *
+make_safety_object(Oid objid, Oid classid, char proparallel)
+{
+ safety_object *object = (safety_object *) palloc(sizeof(safety_object));
+
+ object->objid = objid;
+ object->classid = classid;
+ object->proparallel = proparallel;
+
+ return object;
+}
+
+/* check_functions_in_node callback */
+static bool
+parallel_hazard_checker(Oid func_id, void *context)
+{
+ char proparallel;
+ max_parallel_hazard_context *cont = (max_parallel_hazard_context *) context;
+
+ proparallel = func_parallel(func_id);
+
+ if (max_parallel_hazard_test(proparallel, cont) && !cont->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object = make_safety_object(func_id,
+ ProcedureRelationId,
+ proparallel);
+ cont->objects = lappend(cont->objects, object);
+ }
+
+ return false;
+}
+
+/*
+ * parallel_hazard_walker
+ *
+ * Recursively search an expression tree which is defined as partition key or
+ * index or constraint or column default expression for PARALLEL
+ * UNSAFE/RESTRICTED table-related objects.
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ /* Check for hazardous functions in node itself */
+ if (check_functions_in_node(node, parallel_hazard_checker,
+ context))
+ return true;
+
+ if (IsA(node, CoerceToDomain))
+ {
+ CoerceToDomain *domain = (CoerceToDomain *) node;
+
+ if (target_rel_domain_parallel_hazard(domain->resulttype, context))
+ return true;
+ }
+
+ /* Recurse to check arguments */
+ return expression_tree_walker(node,
+ parallel_hazard_walker,
+ context);
+}
+
/* check_functions_in_node callback */
static bool
max_parallel_hazard_checker(Oid func_id, void *context)
@@ -884,6 +996,547 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * target_rel_parallel_hazard
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+List*
+target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting, char *max_hazard)
+{
+ max_parallel_hazard_context context;
+ Relation targetRel;
+
+ context.check_all = findall;
+ context.objects = NIL;
+ context.max_hazard = PROPARALLEL_SAFE;
+ context.max_interesting = max_interesting;
+ context.safe_param_ids = NIL;
+ context.partition_directory = NULL;
+
+ targetRel = table_open(relOid, AccessShareLock);
+
+ (void) target_rel_parallel_hazard_recurse(targetRel, &context, false);
+ if (context.partition_directory)
+ DestroyPartitionDirectory(context.partition_directory);
+
+ table_close(targetRel, AccessShareLock);
+
+ *max_hazard = context.max_hazard;
+
+ return context.objects;
+}
+
+/*
+ * target_rel_parallel_hazard_recurse
+ *
+ * Recursively search all table-related objects for PARALLEL UNSAFE/RESTRICTED
+ * objects.
+ *
+ * If context->find_all is true, then detect all PARALLEL UNSAFE/RESTRICTED
+ * table-related objects.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_parallel_hazard_recurse(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition)
+{
+ TupleDesc tupdesc;
+ int attnum;
+
+ /*
+ * We can't support table modification in a parallel worker if it's a
+ * foreign table/partition (no FDW API for supporting parallel access) or
+ * a temporary table.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ RelationUsesLocalBuffers(rel))
+ {
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context) &&
+ !context->check_all)
+ return true;
+ else
+ {
+ safety_object *object = make_safety_object(rel->rd_rel->oid,
+ RelationRelationId,
+ PROPARALLEL_RESTRICTED);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /*
+ * If a partitioned table, check that each partition is safe for
+ * modification in parallel-mode.
+ */
+ if (target_rel_partitions_parallel_hazard(rel, context, is_partition))
+ return true;
+
+ /*
+ * If there are any index expressions or index predicate, check that they
+ * are parallel-mode safe.
+ */
+ if (target_rel_index_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * If any triggers exist, check that they are parallel-safe.
+ */
+ if (target_rel_trigger_parallel_hazard(rel, context))
+ return true;
+
+ /*
+ * Column default expressions are only applicable to INSERT and UPDATE.
+ * Note that even though column defaults may be specified separately for
+ * each partition in a partitioned table, a partition's default value is
+ * not applied when inserting a tuple through a partitioned table.
+ */
+
+ tupdesc = RelationGetDescr(rel);
+ for (attnum = 0; attnum < tupdesc->natts; attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attnum);
+
+ /* We don't need info for dropped or generated attributes */
+ if (att->attisdropped || att->attgenerated)
+ continue;
+
+ if (att->atthasdef && !is_partition)
+ {
+ Node *defaultexpr;
+
+ defaultexpr = build_column_default(rel, attnum + 1);
+ if (parallel_hazard_walker((Node *) defaultexpr, context))
+ return true;
+ }
+
+ /*
+ * If the column is of a DOMAIN type, determine whether that
+ * domain has any CHECK expressions that are not parallel-mode
+ * safe.
+ */
+ if (get_typtype(att->atttypid) == TYPTYPE_DOMAIN)
+ {
+ if (target_rel_domain_parallel_hazard(att->atttypid, context))
+ return true;
+ }
+ }
+
+ /*
+ * CHECK constraints are only applicable to INSERT and UPDATE. If any
+ * CHECK constraints exist, determine if they are parallel-safe.
+ */
+ if (target_rel_chk_constr_parallel_hazard(rel, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_trigger_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified relation's trigger data.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_trigger_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ char proparallel;
+
+ if (rel->trigdesc == NULL)
+ return false;
+
+ /*
+ * Care is needed here to avoid using the same relcache TriggerDesc field
+ * across other cache accesses, because relcache doesn't guarantee that it
+ * won't move.
+ */
+ for (i = 0; i < rel->trigdesc->numtriggers; i++)
+ {
+ Oid tgfoid = rel->trigdesc->triggers[i].tgfoid;
+ Oid tgoid = rel->trigdesc->triggers[i].tgoid;
+
+ proparallel = func_parallel(tgfoid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object,
+ *parent_object;
+
+ object = make_safety_object(tgfoid, ProcedureRelationId,
+ proparallel);
+ parent_object = make_safety_object(tgoid, TriggerRelationId,
+ proparallel);
+
+ context->objects = lappend(context->objects, object);
+ context->objects = lappend(context->objects, parent_object);
+ }
+ }
+
+ return false;
+}
+
+/*
+ * index_expr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the input index expression and index predicate.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+index_expr_parallel_hazard(Relation index_rel,
+ List *ii_Expressions,
+ List *ii_Predicate,
+ max_parallel_hazard_context *context)
+{
+ int i;
+ Form_pg_index indexStruct;
+ ListCell *index_expr_item;
+
+ indexStruct = index_rel->rd_index;
+ index_expr_item = list_head(ii_Expressions);
+
+ /* Check parallel-safety of index expression */
+ for (i = 0; i < indexStruct->indnatts; i++)
+ {
+ int keycol = indexStruct->indkey.values[i];
+
+ if (keycol == 0)
+ {
+ /* Found an index expression */
+ Node *index_expr;
+
+ Assert(index_expr_item != NULL);
+ if (index_expr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(index_expr_item);
+
+ if (parallel_hazard_walker(index_expr, context))
+ return true;
+
+ index_expr_item = lnext(ii_Expressions, index_expr_item);
+ }
+ }
+
+ /* Check parallel-safety of index predicate */
+ if (parallel_hazard_walker((Node *) ii_Predicate, context))
+ return true;
+
+ return false;
+}
+
+/*
+ * target_rel_index_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any existing index expressions or index predicate of a specified
+ * relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_index_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ List *index_oid_list;
+ ListCell *lc;
+ LOCKMODE lockmode = AccessShareLock;
+ bool max_hazard_found;
+
+ index_oid_list = RelationGetIndexList(rel);
+ foreach(lc, index_oid_list)
+ {
+ Relation index_rel;
+ List *ii_Expressions;
+ List *ii_Predicate;
+ List *temp_objects;
+ char temp_hazard;
+ Oid index_oid = lfirst_oid(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ index_rel = index_open(index_oid, lockmode);
+
+ /* Check index expression */
+ ii_Expressions = RelationGetIndexExpressions(index_rel);
+ ii_Predicate = RelationGetIndexPredicate(index_rel);
+
+ max_hazard_found = index_expr_parallel_hazard(index_rel,
+ ii_Expressions,
+ ii_Predicate,
+ context);
+
+ index_close(index_rel, lockmode);
+
+ if (max_hazard_found)
+ return true;
+
+ /* Add the index itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+
+ object = make_safety_object(index_oid, IndexRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ list_free(index_oid_list);
+
+ return false;
+}
+
+/*
+ * target_rel_domain_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for the specified DOMAIN type. Only any CHECK expressions are
+ * examined for parallel-safety.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_domain_parallel_hazard(Oid typid,
+ max_parallel_hazard_context *context)
+{
+ ListCell *lc;
+ List *domain_list;
+ List *temp_objects;
+ char temp_hazard;
+
+ domain_list = GetDomainConstraints(typid);
+
+ foreach(lc, domain_list)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) r->check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ else if (context->objects != NIL)
+ {
+ safety_object *object;
+ Oid constr_oid = get_domain_constraint_oid(typid,
+ r->name,
+ false);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+
+}
+
+/*
+ * target_rel_partitions_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any partitions of a specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_partitions_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context,
+ bool is_partition)
+{
+ int i;
+ PartitionDesc pdesc;
+ PartitionKey pkey;
+ ListCell *partexprs_item;
+ int partnatts;
+ List *partexprs,
+ *qual;
+
+ /*
+ * The partition check expression is composed of its parent table's
+ * partition key expression, we do not need to check it again for a
+ * partition because we already checked the parallel safety of its parent
+ * table's partition key expression.
+ */
+ if (!is_partition)
+ {
+ qual = RelationGetPartitionQual(rel);
+ if (parallel_hazard_walker((Node *) qual, context))
+ return true;
+ }
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ pkey = RelationGetPartitionKey(rel);
+
+ partnatts = get_partition_natts(pkey);
+ partexprs = get_partition_exprs(pkey);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ Oid funcOid = pkey->partsupfunc[i].fn_oid;
+
+ if (OidIsValid(funcOid))
+ {
+ char proparallel = func_parallel(funcOid);
+
+ if (max_parallel_hazard_test(proparallel, context) &&
+ !context->check_all)
+ return true;
+
+ else if (proparallel != PROPARALLEL_SAFE)
+ {
+ safety_object *object;
+
+ object = make_safety_object(funcOid, ProcedureRelationId,
+ proparallel);
+ context->objects = lappend(context->objects, object);
+ }
+ }
+
+ /* Check parallel-safety of any expressions in the partition key */
+ if (get_partition_col_attnum(pkey, i) == 0)
+ {
+ Node *check_expr = (Node *) lfirst(partexprs_item);
+
+ if (parallel_hazard_walker(check_expr, context))
+ return true;
+
+ partexprs_item = lnext(partexprs, partexprs_item);
+ }
+ }
+
+ /* Recursively check each partition ... */
+
+ /* Create the PartitionDirectory infrastructure if we didn't already */
+ if (context->partition_directory == NULL)
+ context->partition_directory =
+ CreatePartitionDirectory(CurrentMemoryContext, false);
+
+ pdesc = PartitionDirectoryLookup(context->partition_directory, rel);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Relation part_rel;
+ bool max_hazard_found;
+
+ part_rel = table_open(pdesc->oids[i], AccessShareLock);
+ max_hazard_found = target_rel_parallel_hazard_recurse(part_rel,
+ context,
+ true);
+ table_close(part_rel, AccessShareLock);
+
+ if (max_hazard_found)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * target_rel_chk_constr_parallel_hazard
+ *
+ * If context->find_all is true, then find all the PARALLEL UNSAFE/RESTRICTED
+ * objects for any CHECK expressions or CHECK constraints related to the
+ * specified relation.
+ *
+ * If context->find_all is false, then find the worst parallel-hazard level.
+ */
+static bool
+target_rel_chk_constr_parallel_hazard(Relation rel,
+ max_parallel_hazard_context *context)
+{
+ char temp_hazard;
+ int i;
+ TupleDesc tupdesc;
+ List *temp_objects;
+ ConstrCheck *check;
+
+ tupdesc = RelationGetDescr(rel);
+
+ if (tupdesc->constr == NULL)
+ return false;
+
+ check = tupdesc->constr->check;
+
+ /*
+ * Determine if there are any CHECK constraints which are not
+ * parallel-safe.
+ */
+ for (i = 0; i < tupdesc->constr->num_check; i++)
+ {
+ Expr *check_expr = stringToNode(check[i].ccbin);
+
+ temp_objects = context->objects;
+ context->objects = NIL;
+ temp_hazard = context->max_hazard;
+ context->max_hazard = PROPARALLEL_SAFE;
+
+ if (parallel_hazard_walker((Node *) check_expr, context))
+ return true;
+
+ /* Add the constraint itself to the objects list */
+ if (context->objects != NIL)
+ {
+ Oid constr_oid;
+ safety_object *object;
+
+ constr_oid = get_relation_constraint_oid(rel->rd_rel->oid,
+ check->ccname,
+ true);
+
+ object = make_safety_object(constr_oid,
+ ConstraintRelationId,
+ context->max_hazard);
+
+ context->objects = lappend(context->objects, object);
+ }
+
+ (void) max_parallel_hazard_test(temp_hazard, context);
+
+ context->objects = list_concat(context->objects, temp_objects);
+ list_free(temp_objects);
+ }
+
+ return false;
+}
+
/*
* is_parallel_allowed_for_modify
*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 88faf4dfd7..06d859c966 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -23,6 +23,8 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
@@ -31,6 +33,7 @@
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
@@ -43,6 +46,7 @@
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/timestamp.h"
+#include "utils/varlena.h"
/*
* Common subroutine for num_nulls() and num_nonnulls().
@@ -605,6 +609,96 @@ pg_collation_for(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
}
+/*
+ * Find the worst parallel-hazard level in the given relation
+ *
+ * Returns the worst parallel hazard level (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given relation.
+ */
+Datum
+pg_get_table_max_parallel_dml_hazard(PG_FUNCTION_ARGS)
+{
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ (void) target_rel_parallel_hazard(relOid, false,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+
+ PG_RETURN_CHAR(max_parallel_hazard);
+}
+
+/*
+ * Determine whether the target relation is safe to execute parallel modification.
+ *
+ * Return all the PARALLEL RESTRICTED/UNSAFE objects.
+ */
+Datum
+pg_get_table_parallel_dml_safety(PG_FUNCTION_ARGS)
+{
+#define PG_GET_PARALLEL_SAFETY_COLS 3
+ List *objects;
+ ListCell *object;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ ReturnSetInfo *rsinfo;
+ char max_parallel_hazard;
+ Oid relOid = PG_GETARG_OID(0);
+
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ objects = target_rel_parallel_hazard(relOid, true,
+ PROPARALLEL_UNSAFE,
+ &max_parallel_hazard);
+ foreach(object, objects)
+ {
+ Datum values[PG_GET_PARALLEL_SAFETY_COLS];
+ bool nulls[PG_GET_PARALLEL_SAFETY_COLS];
+ safety_object *sobject = (safety_object *) lfirst(object);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = sobject->objid;
+ values[1] = sobject->classid;
+ values[2] = sobject->proparallel;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
/*
* pg_relation_is_updatable - determine which update events the specified
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 326fae62e2..02a8f70b4f 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -2534,6 +2534,23 @@ compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
return 0;
}
+/*
+ * GetDomainConstraints --- get DomainConstraintState list of specified domain type
+ */
+List *
+GetDomainConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+ List *constraints = NIL;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ if(typentry->domainData != NULL)
+ constraints = typentry->domainData->constraints;
+
+ return constraints;
+}
+
/*
* Load (or re-load) the enumData member of the typcache entry.
*/
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 8cd0252082..4483cd1fc3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3770,6 +3770,20 @@
provolatile => 's', prorettype => 'regclass', proargtypes => 'regclass',
prosrc => 'pg_get_replica_identity_index' },
+{ oid => '6122',
+ descr => 'parallel unsafe/restricted objects in the target relation',
+ proname => 'pg_get_table_parallel_dml_safety', prorows => '100',
+ proretset => 't', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proargtypes => 'regclass',
+ proallargtypes => '{regclass,oid,oid,char}',
+ proargmodes => '{i,o,o,o}',
+ proargnames => '{table_name, objid, classid, proparallel}',
+ prosrc => 'pg_get_table_parallel_dml_safety' },
+
+{ oid => '6123', descr => 'worst parallel-hazard level in the given relation for DML',
+ proname => 'pg_get_table_max_parallel_dml_hazard', prorettype => 'char', proargtypes => 'regclass',
+ prosrc => 'pg_get_table_max_parallel_dml_hazard', provolatile => 'v', proparallel => 'u' },
+
# Deferrable unique constraint trigger
{ oid => '1250', descr => 'deferred UNIQUE constraint check',
proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
@@ -3777,11 +3791,11 @@
# Generic referential integrity constraint triggers
{ oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_ins' },
+ proname => 'RI_FKey_check_ins', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_ins' },
{ oid => '1645', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
- proname => 'RI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
- proargtypes => '', prosrc => 'RI_FKey_check_upd' },
+ proname => 'RI_FKey_check_upd', provolatile => 'v', proparallel => 'r',
+ prorettype => 'trigger', proargtypes => '', prosrc => 'RI_FKey_check_upd' },
{ oid => '1646', descr => 'referential integrity ON DELETE CASCADE',
proname => 'RI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
proargtypes => '', prosrc => 'RI_FKey_cascade_del' },
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 32b56565e5..67e8f5026a 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -23,6 +23,13 @@ typedef struct
List **windowFuncs; /* lists of WindowFuncs for each winref */
} WindowFuncLists;
+typedef struct safety_object
+{
+ Oid objid;
+ Oid classid;
+ char proparallel;
+} safety_object;
+
extern bool contain_agg_clause(Node *clause);
extern bool contain_window_function(Node *clause);
@@ -54,5 +61,8 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
extern bool is_parallel_allowed_for_modify(Query *parse);
+extern List *target_rel_parallel_hazard(Oid relOid, bool findall,
+ char max_interesting,
+ char *max_hazard);
#endif /* CLAUSES_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 1d68a9a4b7..28ca7d8a6e 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -199,6 +199,8 @@ extern uint64 assign_record_type_identifier(Oid type_id, int32 typmod);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
+extern List *GetDomainConstraints(Oid type_id);
+
extern size_t SharedRecordTypmodRegistryEstimate(void);
extern void SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *,
--
2.27.0
v14-0004-regression-test-and-doc-updates.patchapplication/octet-stream; name=v14-0004-regression-test-and-doc-updates.patchDownload
From 82a4f3454b3f24de57bb10068c1b7b5962a73a7a Mon Sep 17 00:00:00 2001
From: Greg Nancarrow <gregn4422@gmail.com>
Date: Fri, 23 Jul 2021 14:14:59 +1000
Subject: [PATCH v14 4/4] regression-test-and-doc-updates
---
contrib/test_decoding/expected/ddl.out | 4 +
doc/src/sgml/func.sgml | 61 ++
doc/src/sgml/ref/alter_foreign_table.sgml | 13 +
doc/src/sgml/ref/alter_table.sgml | 12 +
doc/src/sgml/ref/create_foreign_table.sgml | 37 ++
doc/src/sgml/ref/create_table.sgml | 38 ++
doc/src/sgml/ref/create_table_as.sgml | 23 +
src/test/regress/expected/alter_table.out | 2 +
src/test/regress/expected/compression_1.out | 9 +
src/test/regress/expected/copy2.out | 1 +
src/test/regress/expected/create_table.out | 14 +
.../regress/expected/create_table_like.out | 8 +
src/test/regress/expected/domain.out | 2 +
src/test/regress/expected/foreign_data.out | 42 ++
src/test/regress/expected/identity.out | 1 +
src/test/regress/expected/inherit.out | 13 +
src/test/regress/expected/insert.out | 12 +
src/test/regress/expected/insert_parallel.out | 588 ++++++++++++++++++
src/test/regress/expected/psql.out | 58 +-
src/test/regress/expected/publication.out | 4 +
.../regress/expected/replica_identity.out | 1 +
src/test/regress/expected/rowsecurity.out | 1 +
src/test/regress/expected/rules.out | 3 +
src/test/regress/expected/stats_ext.out | 1 +
src/test/regress/expected/triggers.out | 1 +
src/test/regress/expected/update.out | 1 +
src/test/regress/output/tablespace.source | 2 +
src/test/regress/parallel_schedule | 1 +
src/test/regress/sql/insert_parallel.sql | 345 ++++++++++
29 files changed, 1271 insertions(+), 27 deletions(-)
create mode 100644 src/test/regress/expected/insert_parallel.out
create mode 100644 src/test/regress/sql/insert_parallel.sql
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 4ff0044c78..1d7eebb897 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -446,6 +446,7 @@ WITH (user_catalog_table = true)
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -460,6 +461,7 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
INSERT INTO replication_metadata(relation, options)
VALUES ('bar', ARRAY['a', 'b']);
@@ -473,6 +475,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -492,6 +495,7 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
rewritemeornot | integer | | | | plain | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Parallel DML: unsafe
Options: user_catalog_table=false
INSERT INTO replication_metadata(relation, options)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f62c33e5c7..e40c310f0f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23939,6 +23939,67 @@ SELECT collation for ('foo' COLLATE "de_DE");
Undefined objects are identified with <literal>NULL</literal> values.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_table_parallel_dml_safety</primary>
+ </indexterm>
+ <function>pg_get_table_parallel_dml_safety</function> ( <parameter>table_name</parameter> <type>regclass</type> )
+ <returnvalue>record</returnvalue>
+ ( <parameter>objid</parameter> <type>oid</type>,
+ <parameter>classid</parameter> <type>oid</type>,
+ <parameter>proparallel</parameter> <type>char</type> )
+ </para>
+ <para>
+ Returns a row containing enough information to uniquely identify the
+ parallel unsafe/restricted table-related objects from which the
+ table's parallel DML safety is determined. The user can use this
+ information during development in order to accurately declare a
+ table's parallel DML safety, or to identify any problematic objects
+ if parallel DML fails or behaves unexpectedly. Note that when the
+ use of an object-related parallel unsafe/restricted function is
+ detected, both the function OID and the object OID are returned.
+ <parameter>classid</parameter> is the OID of the system catalog
+ containing the object;
+ <parameter>objid</parameter> is the OID of the object itself.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_table_max_parallel_dml_hazard</primary>
+ </indexterm>
+ <function>pg_get_table_max_parallel_dml_hazard</function> ( <type>regclass</type> )
+ <returnvalue>char</returnvalue>
+ </para>
+ <para>
+ Returns the worst parallel DML safety hazard that can be found in the
+ given relation:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>s</literal> safe
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>r</literal> restricted
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>u</literal> unsafe
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ Users can use this function to do a quick check without caring about
+ specific parallel-related objects.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 7ca03f3ac9..c1652e8312 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -29,6 +29,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -299,6 +301,17 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See the similar form of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>
+ for more details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 1c7f48938b..d32c1551e7 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -37,6 +37,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ATTACH PARTITION <replaceable class="parameter">partition_name</replaceable> { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
DETACH PARTITION <replaceable class="parameter">partition_name</replaceable> [ CONCURRENTLY | FINALIZE ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+ PARALLEL { UNSAFE | RESTRICTED | SAFE }
<phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
@@ -1010,6 +1012,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL</literal></term>
+ <listitem>
+ <para>
+ Change whether the data in the table can be modified in parallel mode.
+ See <link linkend="sql-createtable"><command>CREATE TABLE</command></link> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index f9477efe58..ecc812a295 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -36,6 +37,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] <replaceable class="parameter">partition_bound_spec</replaceable>
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="parameter">option</replaceable> '<replaceable class="parameter">value</replaceable>' [, ... ] ) ]
@@ -290,6 +292,41 @@ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table (e.g., functions in triggers/index expression/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_parallel_dml_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">server_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..d6db677e3a 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -33,6 +33,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
OF <replaceable class="parameter">type_name</replaceable> [ (
@@ -45,6 +46,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name</replaceable>
PARTITION OF <replaceable class="parameter">parent_table</replaceable> [ (
@@ -57,6 +59,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+[ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
@@ -1336,6 +1339,41 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-paralleldmlsafety">
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in the table
+ can't be modified in parallel mode, and this forces a serial execution plan
+ for DML statements operating on the table. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in the
+ table can be modified in parallel mode, but the modification is
+ restricted to the parallel group leader.
+ <literal>PARALLEL DML SAFE</literal> indicates that the data in the table
+ can be modified in parallel mode without restriction. Note that
+ <productname>PostgreSQL</productname> currently does not support data
+ modification by parallel workers.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ the table
+ (e.g., functions in triggers/index expressions/constraints etc.).
+ </para>
+
+ <para>
+ To assist in correctly labeling the parallel DML safety level of a table,
+ PostgreSQL provides some utility functions that may be used during
+ application development. Refer to
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_parallel_dml_safety()</function></link> and
+ <link linkend="functions-info-object-table">
+ <function>pg_get_table_max_parallel_dml_hazard()</function></link> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>USING INDEX TABLESPACE <replaceable class="parameter">tablespace_name</replaceable></literal></term>
<listitem>
diff --git a/doc/src/sgml/ref/create_table_as.sgml b/doc/src/sgml/ref/create_table_as.sgml
index 07558ab56c..71c932b048 100644
--- a/doc/src/sgml/ref/create_table_as.sgml
+++ b/doc/src/sgml/ref/create_table_as.sgml
@@ -27,6 +27,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ PARALLEL DML { UNSAFE | RESTRICTED | SAFE } ]
AS <replaceable>query</replaceable>
[ WITH [ NO ] DATA ]
</synopsis>
@@ -223,6 +224,28 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>PARALLEL DML { UNSAFE | RESTRICTED | SAFE } </literal></term>
+ <listitem>
+ <para>
+ <literal>PARALLEL DML UNSAFE</literal> indicates that the data in table
+ can't be modified in parallel mode. This is the default.
+ <literal>PARALLEL DML RESTRICTED</literal> indicates that the data in
+ table can be modified in parallel mode, but the modification is
+ restricted to parallel group leader. <literal>PARALLEL DML SAFE</literal>
+ indicates that the table is safe to be modified in parallel mode without
+ restriction. But note that <productname>PostgreSQL</productname>
+ does not support data modification in parallel worker for now.
+ </para>
+
+ <para>
+ Tables should be labeled parallel dml unsafe/restricted if any parallel
+ unsafe/restricted function could be executed when modifying the data in
+ table (e.g., functions in trigger/index expression/constraints ...).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>query</replaceable></term>
<listitem>
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8dcb00ac67..6983372bac 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2206,6 +2206,7 @@ alter table test_storage alter column a set storage external;
b | integer | | | 0 | plain | |
Indexes:
"test_storage_idx" btree (b, a)
+Parallel DML: unsafe
\d+ test_storage_idx
Index "public.test_storage_idx"
@@ -4193,6 +4194,7 @@ ALTER TABLE range_parted2 DETACH PARTITION part_rp CONCURRENTLY;
a | integer | | | | plain | |
Partition key: RANGE (a)
Number of partitions: 0
+Parallel DML: unsafe
-- constraint should be created
\d part_rp
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index 1ce2962d55..041e829b37 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -12,6 +12,7 @@ INSERT INTO cmdata VALUES(repeat('1234567890', 1000));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
ERROR: compression method lz4 not supported
@@ -51,6 +52,7 @@ SELECT * INTO cmmove1 FROM cmdata;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | text | | | | extended | | |
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmmove1;
pg_column_compression
@@ -138,6 +140,7 @@ CREATE TABLE cmdata2 (f1 int);
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
\d+ cmdata2
@@ -145,6 +148,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE varchar;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
\d+ cmdata2
@@ -152,6 +156,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 TYPE int USING f1::integer;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | integer | | | | plain | | |
+Parallel DML: unsafe
--changing column storage should not impact the compression method
--but the data should not be compressed
@@ -162,6 +167,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION pglz;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
f1 | character varying | | | | extended | pglz | |
+Parallel DML: unsafe
ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
\d+ cmdata2
@@ -169,6 +175,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET STORAGE plain;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | pglz | |
+Parallel DML: unsafe
INSERT INTO cmdata2 VALUES (repeat('123456789', 800));
SELECT pg_column_compression(f1) FROM cmdata2;
@@ -249,6 +256,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
f1 | text | | | | extended | pglz | |
Indexes:
"idx" btree (f1)
+Parallel DML: unsafe
SELECT pg_column_compression(f1) FROM cmdata;
pg_column_compression
@@ -263,6 +271,7 @@ ALTER TABLE cmdata2 ALTER COLUMN f1 SET COMPRESSION default;
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-------------------+-----------+----------+---------+---------+-------------+--------------+-------------
f1 | character varying | | | | plain | | |
+Parallel DML: unsafe
-- test alter compression method for materialized views
ALTER MATERIALIZED VIEW compressmv ALTER COLUMN x SET COMPRESSION lz4;
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index 5f3685e9ef..9654dbb4b0 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -519,6 +519,7 @@ alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
f1 | integer | | | | plain | |
Check constraints:
"check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+Parallel DML: unsafe
copy check_con_tbl from stdin;
NOTICE: input = {"f1":1}
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 96bf426d98..13e2c1ca66 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,6 +505,7 @@ Number of partitions: 0
b | text | | | | extended | |
Partition key: RANGE (((a + 1)), substr(b, 1, 5))
Number of partitions: 0
+Parallel DML: unsafe
INSERT INTO partitioned2 VALUES (1, 'hello');
ERROR: no partition of relation "partitioned2" found for row
@@ -518,6 +519,7 @@ CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO
b | text | | | | extended | |
Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
+Parallel DML: unsafe
DROP TABLE partitioned, partitioned2;
-- check reference to partitioned table's rowtype in partition descriptor
@@ -559,6 +561,7 @@ select * from partitioned where partitioned = '(1,2)'::partitioned;
b | integer | | | | plain | |
Partition of: partitioned FOR VALUES IN ('(1,2)')
Partition constraint: (((partitioned1.*)::partitioned IS DISTINCT FROM NULL) AND ((partitioned1.*)::partitioned = '(1,2)'::partitioned))
+Parallel DML: unsafe
drop table partitioned;
-- check that dependencies of partition columns are handled correctly
@@ -618,6 +621,7 @@ Partitions: part_null FOR VALUES IN (NULL),
part_p1 FOR VALUES IN (1),
part_p2 FOR VALUES IN (2),
part_p3 FOR VALUES IN (3)
+Parallel DML: unsafe
-- forbidden expressions for partition bound with list partitioned table
CREATE TABLE part_bogus_expr_fail PARTITION OF list_parted FOR VALUES IN (somename);
@@ -1064,6 +1068,7 @@ drop table test_part_coll_posix;
b | integer | | not null | 1 | plain | |
Partition of: parted FOR VALUES IN ('b')
Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Parallel DML: unsafe
-- Both partition bound and partition key in describe output
\d+ part_c
@@ -1076,6 +1081,7 @@ Partition of: parted FOR VALUES IN ('c')
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
Partition key: RANGE (b)
Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+Parallel DML: unsafe
-- a level-2 partition's constraint will include the parent's expressions
\d+ part_c_1_10
@@ -1086,6 +1092,7 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
b | integer | | not null | 0 | plain | |
Partition of: part_c FOR VALUES FROM (1) TO (10)
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Parallel DML: unsafe
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
@@ -1120,6 +1127,7 @@ CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MI
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
+Parallel DML: unsafe
DROP TABLE unbounded_range_part;
CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
@@ -1132,6 +1140,7 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALU
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Parallel DML: unsafe
CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
\d+ range_parted4_2
@@ -1143,6 +1152,7 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Parallel DML: unsafe
CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
\d+ range_parted4_3
@@ -1154,6 +1164,7 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, M
c | integer | | | | plain | |
Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Parallel DML: unsafe
DROP TABLE range_parted4;
-- user-defined operator class in partition key
@@ -1190,6 +1201,7 @@ SELECT obj_description('parted_col_comment'::regclass);
b | text | | | | extended | |
Partition key: LIST (a)
Number of partitions: 0
+Parallel DML: unsafe
DROP TABLE parted_col_comment;
-- list partitioning on array type column
@@ -1202,6 +1214,7 @@ CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
a | integer[] | | | | extended | |
Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
+Parallel DML: unsafe
DROP TABLE arrlp;
-- partition on boolean column
@@ -1216,6 +1229,7 @@ create table boolspart_f partition of boolspart for values in (false);
Partition key: LIST (a)
Partitions: boolspart_f FOR VALUES IN (false),
boolspart_t FOR VALUES IN (true)
+Parallel DML: unsafe
drop table boolspart;
-- partitions mixing temporary and permanent relations
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 7ad5fafe93..ba69106a1d 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -333,6 +333,7 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
a | text | | not null | | main | |
b | text | | | | extended | |
c | text | | | | external | |
+Parallel DML: unsafe
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@@ -342,6 +343,7 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
a | text | | not null | | extended | | A
b | text | | | | extended | | B
c | text | | | | extended | | C
+Parallel DML: unsafe
CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -356,6 +358,7 @@ NOTICE: merging constraint "ctlt1_a_check" with inherited definition
Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
description
@@ -378,6 +381,7 @@ Check constraints:
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1,
ctlt3
+Parallel DML: unsafe
CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -395,6 +399,7 @@ Check constraints:
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
Inherits: ctlt1
+Parallel DML: unsafe
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
description
@@ -418,6 +423,7 @@ Check constraints:
Statistics objects:
"public"."ctlt_all_a_b_stat" ON a, b FROM ctlt_all
"public"."ctlt_all_expr_stat" ON ((a || b)) FROM ctlt_all
+Parallel DML: unsafe
SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
relname | objsubid | description
@@ -458,6 +464,7 @@ Check constraints:
Statistics objects:
"public"."pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
"public"."pg_attrdef_expr_stat" ON ((a || b)) FROM public.pg_attrdef
+Parallel DML: unsafe
DROP TABLE public.pg_attrdef;
-- Check that LIKE isn't confused when new table masks the old, either
@@ -480,6 +487,7 @@ Check constraints:
Statistics objects:
"ctl_schema"."ctlt1_a_b_stat" ON a, b FROM ctlt1
"ctl_schema"."ctlt1_expr_stat" ON ((a || b)) FROM ctlt1
+Parallel DML: unsafe
ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 411d5c003e..2419d96a33 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -276,6 +276,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1.r = (dcomptable.d1).r - 1::double precision, d1.i = (dcomptable.d1).i + 1::double precision
WHERE (dcomptable.d1).i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
@@ -413,6 +414,7 @@ Rules:
silly AS
ON DELETE TO dcomptable DO INSTEAD UPDATE dcomptable SET d1[1].r = dcomptable.d1[1].r - 1::double precision, d1[1].i = dcomptable.d1[1].i + 1::double precision
WHERE dcomptable.d1[1].i > 0::double precision
+Parallel DML: unsafe
drop table dcomptable;
drop type comptype cascade;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 426080ae39..311485c707 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -735,6 +735,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
\det+
List of foreign tables
@@ -857,6 +858,7 @@ Check constraints:
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- can't change the column type if it's used elsewhere
CREATE TABLE use_ft1_column_type (x ft1);
@@ -1396,6 +1398,7 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1407,6 +1410,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2;
\d+ fd_pt1
@@ -1416,6 +1420,7 @@ DROP FOREIGN TABLE ft2;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Parallel DML: unsafe
CREATE FOREIGN TABLE ft2 (
c1 integer NOT NULL,
@@ -1431,6 +1436,7 @@ CREATE FOREIGN TABLE ft2 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
\d+ fd_pt1
@@ -1441,6 +1447,7 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1452,6 +1459,7 @@ Child tables: ft2
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
CREATE TABLE ct3() INHERITS(ft2);
CREATE FOREIGN TABLE ft3 (
@@ -1475,6 +1483,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1484,6 +1493,7 @@ Child tables: ct3,
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1494,6 +1504,7 @@ Inherits: ft2
c3 | date | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- add attributes recursively
ALTER TABLE fd_pt1 ADD COLUMN c4 integer;
@@ -1514,6 +1525,7 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1532,6 +1544,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
\d+ ct3
Table "public.ct3"
@@ -1546,6 +1559,7 @@ Child tables: ct3,
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
Inherits: ft2
+Parallel DML: unsafe
\d+ ft3
Foreign table "public.ft3"
@@ -1561,6 +1575,7 @@ Inherits: ft2
c8 | integer | | | | | plain | |
Server: s0
Inherits: ft2
+Parallel DML: unsafe
-- alter attributes recursively
ALTER TABLE fd_pt1 ALTER COLUMN c4 SET DEFAULT 0;
@@ -1588,6 +1603,7 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
c7 | integer | | | | plain | |
c8 | text | | | | external | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1606,6 +1622,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- drop attributes recursively
ALTER TABLE fd_pt1 DROP COLUMN c4;
@@ -1621,6 +1638,7 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1634,6 +1652,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
-- add constraints recursively
ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk1 CHECK (c1 > 0) NO INHERIT;
@@ -1661,6 +1680,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1676,6 +1696,7 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
Child tables: ct3,
ft3
+Parallel DML: unsafe
DROP FOREIGN TABLE ft2; -- ERROR
ERROR: cannot drop foreign table ft2 because other objects depend on it
@@ -1708,6 +1729,7 @@ Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1721,6 +1743,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- drop constraints recursively
ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk1 CASCADE;
@@ -1738,6 +1761,7 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1752,6 +1776,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- VALIDATE CONSTRAINT need do nothing on foreign tables
ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
@@ -1765,6 +1790,7 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1779,6 +1805,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- changes name of an attribute recursively
ALTER TABLE fd_pt1 RENAME COLUMN c1 TO f1;
@@ -1796,6 +1823,7 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
Child tables: ft2
+Parallel DML: unsafe
\d+ ft2
Foreign table "public.ft2"
@@ -1810,6 +1838,7 @@ Check constraints:
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
+Parallel DML: unsafe
-- TRUNCATE doesn't work on foreign tables, either directly or recursively
TRUNCATE ft2; -- ERROR
@@ -1859,6 +1888,7 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1871,6 +1901,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- partition cannot have additional columns
DROP FOREIGN TABLE fd_pt2_1;
@@ -1890,6 +1921,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c4 | character(1) | | | | | extended | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
@@ -1904,6 +1936,7 @@ DROP FOREIGN TABLE fd_pt2_1;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
CREATE FOREIGN TABLE fd_pt2_1 (
c1 integer NOT NULL,
@@ -1919,6 +1952,7 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c3 | date | | | | | plain | |
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- no attach partition validation occurs for foreign tables
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
@@ -1931,6 +1965,7 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1943,6 +1978,7 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot add column to a partition
ALTER TABLE fd_pt2_1 ADD c4 char;
@@ -1959,6 +1995,7 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
c3 | date | | | | plain | |
Partition key: LIST (c1)
Partitions: fd_pt2_1 FOR VALUES IN (1)
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -1973,6 +2010,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
-- cannot drop inherited NOT NULL constraint from a partition
ALTER TABLE fd_pt2_1 ALTER c1 DROP NOT NULL;
@@ -1989,6 +2027,7 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
c3 | date | | | | plain | |
Partition key: LIST (c1)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -2001,6 +2040,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: column "c2" in child table must be marked NOT NULL
@@ -2019,6 +2059,7 @@ Partition key: LIST (c1)
Check constraints:
"fd_pt2chk1" CHECK (c1 > 0)
Number of partitions: 0
+Parallel DML: unsafe
\d+ fd_pt2_1
Foreign table "public.fd_pt2_1"
@@ -2031,6 +2072,7 @@ Check constraints:
"p21chk" CHECK (c2 <> ''::text)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
+Parallel DML: unsafe
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
ERROR: child table is missing constraint "fd_pt2chk1"
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 99811570b7..da24c16d09 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -506,6 +506,7 @@ TABLE itest8;
f3 | integer | | not null | generated by default as identity | plain | |
f4 | bigint | | not null | generated always as identity | plain | |
f5 | bigint | | | | plain | |
+Parallel DML: unsafe
\d itest8_f2_seq
Sequence "public.itest8_f2_seq"
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 06f44287bc..33a216ea4d 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1059,6 +1059,7 @@ ALTER TABLE inhts RENAME d TO dd;
dd | integer | | | | plain | |
Inherits: inht1,
inhs1
+Parallel DML: unsafe
DROP TABLE inhts;
-- Test for renaming in diamond inheritance
@@ -1079,6 +1080,7 @@ ALTER TABLE inht1 RENAME aa TO aaa;
z | integer | | | | plain | |
Inherits: inht2,
inht3
+Parallel DML: unsafe
CREATE TABLE inhts (d int) INHERITS (inht2, inhs1);
NOTICE: merging multiple inherited definitions of column "b"
@@ -1096,6 +1098,7 @@ ERROR: cannot rename inherited column "b"
d | integer | | | | plain | |
Inherits: inht2,
inhs1
+Parallel DML: unsafe
WITH RECURSIVE r AS (
SELECT 'inht1'::regclass AS inhrelid
@@ -1142,6 +1145,7 @@ CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
Indexes:
"test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
Child tables: test_constraints_inh
+Parallel DML: unsafe
ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
\d+ test_constraints
@@ -1152,6 +1156,7 @@ ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Child tables: test_constraints_inh
+Parallel DML: unsafe
\d+ test_constraints_inh
Table "public.test_constraints_inh"
@@ -1161,6 +1166,7 @@ Child tables: test_constraints_inh
val1 | character varying | | | | extended | |
val2 | integer | | | | plain | |
Inherits: test_constraints
+Parallel DML: unsafe
DROP TABLE test_constraints_inh;
DROP TABLE test_constraints;
@@ -1177,6 +1183,7 @@ CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
Indexes:
"test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
\d+ test_ex_constraints
@@ -1185,6 +1192,7 @@ ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Child tables: test_ex_constraints_inh
+Parallel DML: unsafe
\d+ test_ex_constraints_inh
Table "public.test_ex_constraints_inh"
@@ -1192,6 +1200,7 @@ Child tables: test_ex_constraints_inh
--------+--------+-----------+----------+---------+---------+--------------+-------------
c | circle | | | | plain | |
Inherits: test_ex_constraints
+Parallel DML: unsafe
DROP TABLE test_ex_constraints_inh;
DROP TABLE test_ex_constraints;
@@ -1208,6 +1217,7 @@ Indexes:
"test_primary_constraints_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Parallel DML: unsafe
\d+ test_foreign_constraints
Table "public.test_foreign_constraints"
@@ -1217,6 +1227,7 @@ Referenced by:
Foreign-key constraints:
"test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
\d+ test_foreign_constraints
@@ -1225,6 +1236,7 @@ ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Child tables: test_foreign_constraints_inh
+Parallel DML: unsafe
\d+ test_foreign_constraints_inh
Table "public.test_foreign_constraints_inh"
@@ -1232,6 +1244,7 @@ Child tables: test_foreign_constraints_inh
--------+---------+-----------+----------+---------+---------+--------------+-------------
id1 | integer | | | | plain | |
Inherits: test_foreign_constraints
+Parallel DML: unsafe
DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3dc22..cb8cd958aa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -177,6 +177,7 @@ Rules:
irule3 AS
ON INSERT TO inserttest2 DO INSERT INTO inserttest (f4[1].if1, f4[1].if2[2]) SELECT new.f1,
new.f2
+Parallel DML: unsafe
drop table inserttest2;
drop table inserttest;
@@ -482,6 +483,7 @@ Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
part_null FOR VALUES IN (NULL),
part_xx_yy FOR VALUES IN ('xx', 'yy'), PARTITIONED,
part_default DEFAULT, PARTITIONED
+Parallel DML: unsafe
-- cleanup
drop table range_parted, list_parted;
@@ -497,6 +499,7 @@ create table part_default partition of list_parted default;
a | integer | | | | plain | |
Partition of: list_parted DEFAULT
No partition constraint
+Parallel DML: unsafe
insert into part_default values (null);
insert into part_default values (1);
@@ -888,6 +891,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
mcrparted6_common_ge_10 FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE),
mcrparted7_gt_common_lt_d FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE),
mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
+Parallel DML: unsafe
\d+ mcrparted1_lt_b
Table "public.mcrparted1_lt_b"
@@ -897,6 +901,7 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
+Parallel DML: unsafe
\d+ mcrparted2_b
Table "public.mcrparted2_b"
@@ -906,6 +911,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
+Parallel DML: unsafe
\d+ mcrparted3_c_to_common
Table "public.mcrparted3_c_to_common"
@@ -915,6 +921,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
+Parallel DML: unsafe
\d+ mcrparted4_common_lt_0
Table "public.mcrparted4_common_lt_0"
@@ -924,6 +931,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text)
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
+Parallel DML: unsafe
\d+ mcrparted5_common_0_to_10
Table "public.mcrparted5_common_0_to_10"
@@ -933,6 +941,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
+Parallel DML: unsafe
\d+ mcrparted6_common_ge_10
Table "public.mcrparted6_common_ge_10"
@@ -942,6 +951,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
+Parallel DML: unsafe
\d+ mcrparted7_gt_common_lt_d
Table "public.mcrparted7_gt_common_lt_d"
@@ -951,6 +961,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
+Parallel DML: unsafe
\d+ mcrparted8_ge_d
Table "public.mcrparted8_ge_d"
@@ -960,6 +971,7 @@ Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::te
b | integer | | | | plain | |
Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
+Parallel DML: unsafe
insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
('comm', -10), ('common', -10), ('common', 0), ('common', 10),
diff --git a/src/test/regress/expected/insert_parallel.out b/src/test/regress/expected/insert_parallel.out
new file mode 100644
index 0000000000..f5d22ce649
--- /dev/null
+++ b/src/test/regress/expected/insert_parallel.out
@@ -0,0 +1,588 @@
+--
+-- PARALLEL
+--
+--
+-- START: setup some tables and data needed by the tests.
+--
+-- Setup - index expressions test
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+-- Setup - column default tests
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+--
+-- END: setup some tables and data needed by the tests.
+--
+begin;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_trigger | r
+ pg_proc | r
+ pg_trigger | r
+(4 rows)
+
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_p1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+--------------------------
+ Insert on para_insert_p1
+ -> Seq Scan on tenk1
+(2 rows)
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+NOTICE: truncate cascades to table "para_insert_f1"
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+ QUERY PLAN
+----------------------------------------------
+ Insert on para_insert_p1
+ -> Gather Merge
+ Workers Planned: 4
+ -> Sort
+ Sort Key: tenk1.unique1
+ -> Parallel Seq Scan on tenk1
+(6 rows)
+
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+ count
+-------
+ 1
+(1 row)
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_data1
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+ Filter: (a = 10)
+(5 rows)
+
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+ data
+------
+ 10
+(1 row)
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on para_insert_f1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+ count | sum
+-------+----------
+ 10000 | 49995000
+(1 row)
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on test_conflict_table
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+ QUERY PLAN
+------------------------------------------------------
+ Insert on test_conflict_table
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: test_conflict_table_pkey
+ -> Seq Scan on test_data
+(4 rows)
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_index | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names2');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | r
+ pg_index | r
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names4');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+ QUERY PLAN
+----------------------------------------
+ Insert on names5
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names6
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names6 select * from names order by last_name returning *;
+ index | first_name | last_name
+-------+------------+-------------
+ 2 | niels | bohr
+ 1 | albert | einstein
+ 4 | leonhard | euler
+ 8 | richard | feynman
+ 5 | stephen | hawking
+ 6 | isaac | newton
+ 3 | erwin | schrodinger
+ 7 | alan | turing
+(8 rows)
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ QUERY PLAN
+----------------------------------------------
+ Insert on names7
+ -> Gather Merge
+ Workers Planned: 3
+ -> Sort
+ Sort Key: names.last_name
+ -> Parallel Seq Scan on names
+(6 rows)
+
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+ last_name_then_first_name
+---------------------------
+ bohr, niels
+ einstein, albert
+ euler, leonhard
+ feynman, richard
+ hawking, stephen
+ newton, isaac
+ schrodinger, erwin
+ turing, alan
+(8 rows)
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_class | r
+(1 row)
+
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ r
+(1 row)
+
+explain (costs off) insert into temp_names select * from names;
+ QUERY PLAN
+----------------------------------------
+ Insert on temp_names
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on names
+(4 rows)
+
+insert into temp_names select * from names;
+--
+-- Test INSERT with column defaults
+--
+--
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+ QUERY PLAN
+--------------------------------------------
+ Insert on testdef
+ -> Gather
+ Workers Planned: 3
+ -> Parallel Seq Scan on test_data
+(4 rows)
+
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+ a | b | c | d
+----+----+----+----
+ 1 | 2 | 10 | 8
+ 2 | 4 | 10 | 16
+ 3 | 6 | 10 | 24
+ 4 | 8 | 10 | 32
+ 5 | 10 | 10 | 40
+ 6 | 12 | 10 | 48
+ 7 | 14 | 10 | 56
+ 8 | 16 | 10 | 64
+ 9 | 18 | 10 | 72
+ 10 | 20 | 10 | 80
+(10 rows)
+
+truncate testdef;
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+ QUERY PLAN
+-----------------------------
+ Insert on testdef
+ -> Seq Scan on test_data
+(2 rows)
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+alter table parttable1 parallel dml safe;
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+ QUERY PLAN
+----------------------------------------
+ Insert on parttable1
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Seq Scan on tenk1
+(4 rows)
+
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+ count
+-------
+ 5000
+(1 row)
+
+select count(*) from parttable1_2;
+ count
+-------
+ 5000
+(1 row)
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+(0 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ s
+(1 row)
+
+insert into names_with_safe_trigger select * from names;
+NOTICE: hello from insert_before_trigger_safe
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_trigger | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+create table dom_table_u (x inotnull_u, y int);
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+ pg_class_relname | proparallel
+------------------+-------------
+ pg_proc | u
+ pg_constraint | u
+(2 rows)
+
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+ pg_get_table_max_parallel_dml_hazard
+--------------------------------------
+ u
+(1 row)
+
+rollback;
+--
+-- Clean up anything not created in the transaction
+--
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1901673622 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2818,6 +2818,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2825,6 +2826,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
--------+----------------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
+Parallel DML: unsafe
\set HIDE_TABLEAM off
\d+ tbl_heap_psql
@@ -2834,6 +2836,7 @@ CREATE MATERIALIZED VIEW mat_view_heap_psql USING heap_psql AS SELECT f1 from tb
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap_psql
+Parallel DML: unsafe
\d+ tbl_heap
Table "tableam_display.tbl_heap"
@@ -2842,50 +2845,51 @@ Access method: heap_psql
f1 | integer | | | | plain | |
f2 | character(100) | | | | extended | |
Access method: heap
+Parallel DML: unsafe
-- AM is displayed for tables, indexes and materialized views.
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | | unsafe | 0 bytes |
(4 rows)
\dt+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+---------------+-------+----------------------+-------------+---------------+---------+-------------
- tableam_display | tbl_heap | table | regress_display_role | permanent | heap | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+---------------+-------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | tbl_heap | table | regress_display_role | permanent | heap | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(2 rows)
\dm+
- List of relations
- Schema | Name | Type | Owner | Persistence | Access method | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Access method | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+---------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | heap_psql | unsafe | 0 bytes |
(1 row)
-- But not for views and sequences.
\dv+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+----------------+------+----------------------+-------------+---------+-------------
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+----------------+------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(1 row)
\set HIDE_TABLEAM on
\d+
- List of relations
- Schema | Name | Type | Owner | Persistence | Size | Description
------------------+--------------------+-------------------+----------------------+-------------+---------+-------------
- tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap | table | regress_display_role | permanent | 0 bytes |
- tableam_display | tbl_heap_psql | table | regress_display_role | permanent | 0 bytes |
- tableam_display | view_heap_psql | view | regress_display_role | permanent | 0 bytes |
+ List of relations
+ Schema | Name | Type | Owner | Persistence | Parallel DML | Size | Description
+-----------------+--------------------+-------------------+----------------------+-------------+--------------+---------+-------------
+ tableam_display | mat_view_heap_psql | materialized view | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | tbl_heap_psql | table | regress_display_role | permanent | unsafe | 0 bytes |
+ tableam_display | view_heap_psql | view | regress_display_role | permanent | unsafe | 0 bytes |
(4 rows)
RESET ROLE;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 4a5ef0bc24..3404007785 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -85,6 +85,7 @@ Indexes:
"testpub_tbl2_pkey" PRIMARY KEY, btree (id)
Publications:
"testpub_foralltables"
+Parallel DML: unsafe
\dRp+ testpub_foralltables
Publication testpub_foralltables
@@ -198,6 +199,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\d+ testpub_tbl1
Table "public.testpub_tbl1"
@@ -211,6 +213,7 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Parallel DML: unsafe
\dRp+ testpub_default
Publication testpub_default
@@ -236,6 +239,7 @@ Indexes:
Publications:
"testpib_ins_trunct"
"testpub_fortbl"
+Parallel DML: unsafe
-- permissions
SET ROLE regress_publication_user2;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 79002197a7..0f8718f2a4 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -171,6 +171,7 @@ Indexes:
"test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
"test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
Replica Identity: FULL
+Parallel DML: unsafe
ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 89397e41f0..396dc3a539 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -958,6 +958,7 @@ Policies:
Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
part_document_nonfiction FOR VALUES FROM (99) TO (100),
part_document_satire FOR VALUES FROM (55) TO (56)
+Parallel DML: unsafe
SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index e5ab11275d..cad4be0a8b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3155,6 +3155,7 @@ Rules:
r3 AS
ON DELETE TO rules_src DO
NOTIFY rules_src_deletion
+Parallel DML: unsafe
--
-- Ensure an aliased target relation for insert is correctly deparsed.
@@ -3183,6 +3184,7 @@ Rules:
r5 AS
ON UPDATE TO rules_src DO INSTEAD UPDATE rules_log trgt SET tag = 'updated'::text
WHERE trgt.f1 = new.f1
+Parallel DML: unsafe
--
-- Also check multiassignment deparsing.
@@ -3206,6 +3208,7 @@ Rules:
WHERE trgt.f1 = new.f1
RETURNING new.f1,
new.f2
+Parallel DML: unsafe
drop table rule_t1, rule_dest;
--
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 62b05c79f9..c247871ee3 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -145,6 +145,7 @@ ALTER STATISTICS ab1_a_b_stats SET STATISTICS -1;
b | integer | | | | plain | |
Statistics objects:
"public"."ab1_a_b_stats" ON a, b FROM ab1
+Parallel DML: unsafe
-- partial analyze doesn't build stats either
ANALYZE ab1 (a);
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 564eb4faa2..df24b857b9 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3483,6 +3483,7 @@ alter trigger parenttrig on parent rename to anothertrig;
Triggers:
parenttrig AFTER INSERT ON child FOR EACH ROW EXECUTE FUNCTION f()
Inherits: parent
+Parallel DML: unsafe
drop table parent, child;
drop function f();
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index c809f88f54..3fcd8e10f3 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -753,6 +753,7 @@ create table part_def partition of range_parted default;
e | character varying | | | | extended | |
Partition of: range_parted DEFAULT
Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
+Parallel DML: unsafe
insert into range_parted values ('c', 9);
-- ok
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..11a750b58d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -339,6 +339,7 @@ Indexes:
"part_a_idx" btree (a), tablespace "regress_tblspace"
Partitions: testschema.part1 FOR VALUES IN (1),
testschema.part2 FOR VALUES IN (2)
+Parallel DML: unsafe
\d testschema.part1
Table "testschema.part1"
@@ -358,6 +359,7 @@ Partition of: testschema.part FOR VALUES IN (1)
Partition constraint: ((a IS NOT NULL) AND (a = 1))
Indexes:
"part1_a_idx" btree (a), tablespace "regress_tblspace"
+Parallel DML: unsafe
\d testschema.part_a_idx
Partitioned index "testschema.part_a_idx"
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..daf0bad4d5 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,6 +96,7 @@ test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
# run by itself so it can run parallel workers
test: select_parallel
test: write_parallel
+test: insert_parallel
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/sql/insert_parallel.sql b/src/test/regress/sql/insert_parallel.sql
new file mode 100644
index 0000000000..b4565f78ab
--- /dev/null
+++ b/src/test/regress/sql/insert_parallel.sql
@@ -0,0 +1,345 @@
+--
+-- PARALLEL
+--
+
+--
+-- START: setup some tables and data needed by the tests.
+--
+
+-- Setup - index expressions test
+
+create function pg_class_relname(Oid)
+returns name language sql parallel unsafe
+as 'select relname from pg_class where $1 = oid';
+
+-- For testing purposes, we'll mark this function as parallel-unsafe
+create or replace function fullname_parallel_unsafe(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel unsafe;
+
+create or replace function fullname_parallel_restricted(f text, l text) returns text as $$
+ begin
+ return f || l;
+ end;
+$$ language plpgsql immutable parallel restricted;
+
+create table names(index int, first_name text, last_name text);
+create table names2(index int, first_name text, last_name text);
+create index names2_fullname_idx on names2 (fullname_parallel_unsafe(first_name, last_name));
+create table names4(index int, first_name text, last_name text);
+create index names4_fullname_idx on names4 (fullname_parallel_restricted(first_name, last_name));
+
+alter table names2 parallel dml safe;
+alter table names4 parallel dml safe;
+
+
+insert into names values
+ (1, 'albert', 'einstein'),
+ (2, 'niels', 'bohr'),
+ (3, 'erwin', 'schrodinger'),
+ (4, 'leonhard', 'euler'),
+ (5, 'stephen', 'hawking'),
+ (6, 'isaac', 'newton'),
+ (7, 'alan', 'turing'),
+ (8, 'richard', 'feynman');
+
+-- Setup - column default tests
+
+create or replace function bdefault_unsafe ()
+returns int language plpgsql parallel unsafe as $$
+begin
+ RETURN 5;
+end $$;
+
+create or replace function cdefault_restricted ()
+returns int language plpgsql parallel restricted as $$
+begin
+ RETURN 10;
+end $$;
+
+create or replace function ddefault_safe ()
+returns int language plpgsql parallel safe as $$
+begin
+ RETURN 20;
+end $$;
+
+create table testdef(a int, b int default bdefault_unsafe(), c int default cdefault_restricted(), d int default ddefault_safe());
+create table test_data(a int);
+insert into test_data select * from generate_series(1,10);
+
+--
+-- END: setup some tables and data needed by the tests.
+--
+
+begin;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+create table para_insert_p1 (
+ unique1 int4 PRIMARY KEY,
+ stringu1 name
+);
+
+create table para_insert_f1 (
+ unique1 int4 REFERENCES para_insert_p1(unique1),
+ stringu1 name
+);
+
+-- Check FK trigger
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('para_insert_f1');
+select pg_get_table_max_parallel_dml_hazard('para_insert_f1');
+
+--
+-- Test INSERT with underlying query.
+-- Set parallel dml safe.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Set parallel dml unsafe.
+-- (should not create plan with parallel SELECT)
+--
+alter table para_insert_p1 parallel dml unsafe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1;
+
+--
+-- Test INSERT with ordered underlying query.
+-- (should create plan with parallel SELECT, GatherMerge parent node)
+--
+truncate para_insert_p1 cascade;
+alter table para_insert_p1 parallel dml safe;
+explain (costs off) insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+insert into para_insert_p1 select unique1, stringu1 from tenk1 order by unique1;
+-- select some values to verify that the parallel insert worked
+select count(*), sum(unique1) from para_insert_p1;
+-- verify that the same transaction has been used by all parallel workers
+select count(*) from (select distinct cmin,xmin from para_insert_p1) as dt;
+
+--
+-- Test INSERT with RETURNING clause.
+-- (should create plan with parallel SELECT, Gather parent node)
+--
+create table test_data1(like test_data);
+alter table test_data1 parallel dml safe;
+explain (costs off) insert into test_data1 select * from test_data where a = 10 returning a as data;
+insert into test_data1 select * from test_data where a = 10 returning a as data;
+
+--
+-- Test INSERT into a table with a foreign key.
+-- (Insert into a table with a foreign key is parallel-restricted,
+-- as doing this in a parallel worker would create a new commandId
+-- and within a worker this is not currently supported)
+--
+alter table para_insert_f1 parallel dml restricted;
+explain (costs off) insert into para_insert_f1 select unique1, stringu1 from tenk1;
+insert into para_insert_f1 select unique1, stringu1 from tenk1;
+-- select some values to verify that the insert worked
+select count(*), sum(unique1) from para_insert_f1;
+
+--
+-- Test INSERT with ON CONFLICT ... DO UPDATE ...
+-- (should not create a parallel plan)
+--
+create table test_conflict_table(id serial primary key, somedata int);
+alter table test_conflict_table parallel dml safe;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data;
+insert into test_conflict_table(id, somedata) select a, a from test_data;
+explain (costs off) insert into test_conflict_table(id, somedata) select a, a from test_data ON CONFLICT(id) DO UPDATE SET somedata = EXCLUDED.somedata + 1;
+
+--
+-- Test INSERT with parallel-unsafe index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names2');
+select pg_get_table_max_parallel_dml_hazard('names2');
+
+--
+-- Test INSERT with parallel-restricted index expression
+--
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names4');
+select pg_get_table_max_parallel_dml_hazard('names4');
+
+--
+-- Test INSERT with underlying query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names5 (like names);
+alter table names5 parallel dml safe;
+explain (costs off) insert into names5 select * from names returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (no projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names6 (like names);
+alter table names6 parallel dml safe;
+explain (costs off) insert into names6 select * from names order by last_name returning *;
+insert into names6 select * from names order by last_name returning *;
+
+--
+-- Test INSERT with underlying ordered query - and RETURNING (with projection)
+-- (should create a parallel plan; parallel SELECT)
+--
+create table names7 (like names);
+alter table names7 parallel dml safe;
+explain (costs off) insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+insert into names7 select * from names order by last_name returning last_name || ', ' || first_name as last_name_then_first_name;
+
+
+--
+-- Test INSERT into temporary table with underlying query.
+-- (Insert into a temp table is parallel-restricted;
+-- should create a parallel plan; parallel SELECT)
+--
+create temporary table temp_names (like names);
+alter table temp_names parallel dml restricted;
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('temp_names');
+select pg_get_table_max_parallel_dml_hazard('temp_names');
+explain (costs off) insert into temp_names select * from names;
+insert into temp_names select * from names;
+
+--
+-- Test INSERT with column defaults
+--
+--
+
+--
+-- Parallel INSERT with unsafe column default, should not use a parallel plan
+--
+alter table testdef parallel dml safe;
+explain (costs off) insert into testdef(a,c,d) select a,a*4,a*8 from test_data;
+
+--
+-- Parallel INSERT with restricted column default, should use parallel SELECT
+--
+explain (costs off) insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+insert into testdef(a,b,d) select a,a*2,a*8 from test_data;
+select * from testdef order by a;
+truncate testdef;
+
+--
+-- Parallel INSERT with restricted and unsafe column defaults, should not use a parallel plan
+--
+explain (costs off) insert into testdef(a,d) select a,a*8 from test_data;
+
+--
+-- Test INSERT into partition with underlying query.
+--
+create table parttable1 (a int, b name) partition by range (a);
+create table parttable1_1 partition of parttable1 for values from (0) to (5000);
+create table parttable1_2 partition of parttable1 for values from (5000) to (10000);
+
+alter table parttable1 parallel dml safe;
+
+explain (costs off) insert into parttable1 select unique1,stringu1 from tenk1;
+insert into parttable1 select unique1,stringu1 from tenk1;
+select count(*) from parttable1_1;
+select count(*) from parttable1_2;
+
+--
+-- Test table with parallel-unsafe check constraint
+--
+create or replace function check_b_unsafe(b name) returns boolean as $$
+ begin
+ return (b <> 'XXXXXX');
+ end;
+$$ language plpgsql parallel unsafe;
+
+create table table_check_b(a int4, b name check (check_b_unsafe(b)), c name);
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('table_check_b');
+select pg_get_table_max_parallel_dml_hazard('table_check_b');
+
+--
+-- Test table with parallel-safe before stmt-level triggers
+-- (should create a parallel SELECT plan; triggers should fire)
+--
+create table names_with_safe_trigger (like names);
+alter table names_with_safe_trigger parallel dml safe;
+
+create or replace function insert_before_trigger_safe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_safe';
+ return new;
+ end;
+$$ language plpgsql parallel safe;
+create trigger insert_before_trigger_safe before insert on names_with_safe_trigger
+ for each statement execute procedure insert_before_trigger_safe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_safe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_safe_trigger');
+insert into names_with_safe_trigger select * from names;
+
+--
+-- Test table with parallel-unsafe before stmt-level triggers
+--
+create table names_with_unsafe_trigger (like names);
+create or replace function insert_before_trigger_unsafe() returns trigger as $$
+ begin
+ raise notice 'hello from insert_before_trigger_unsafe';
+ return new;
+ end;
+$$ language plpgsql parallel unsafe;
+create trigger insert_before_trigger_unsafe before insert on names_with_unsafe_trigger
+ for each statement execute procedure insert_before_trigger_unsafe();
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('names_with_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('names_with_unsafe_trigger');
+
+--
+-- Test partition with parallel-unsafe trigger
+--
+create table part_unsafe_trigger (a int4, b name) partition by range (a);
+create table part_unsafe_trigger_1 partition of part_unsafe_trigger for values from (0) to (5000);
+create table part_unsafe_trigger_2 partition of part_unsafe_trigger for values from (5000) to (10000);
+create trigger part_insert_before_trigger_unsafe before insert on part_unsafe_trigger_1
+ for each statement execute procedure insert_before_trigger_unsafe();
+
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('part_unsafe_trigger');
+select pg_get_table_max_parallel_dml_hazard('part_unsafe_trigger');
+
+--
+-- Test DOMAIN column with a CHECK constraint
+--
+create function sql_is_distinct_from_u(anyelement, anyelement)
+returns boolean language sql parallel unsafe
+as 'select $1 is distinct from $2 limit 1';
+
+create domain inotnull_u int
+ check (sql_is_distinct_from_u(value, null));
+
+create table dom_table_u (x inotnull_u, y int);
+
+
+-- Test DOMAIN column with parallel-unsafe CHECK constraint
+select pg_class_relname(classid), proparallel from pg_get_table_parallel_dml_safety('dom_table_u');
+select pg_get_table_max_parallel_dml_hazard('dom_table_u');
+
+rollback;
+
+--
+-- Clean up anything not created in the transaction
+--
+
+drop table names;
+drop index names2_fullname_idx;
+drop table names2;
+drop index names4_fullname_idx;
+drop table names4;
+drop table testdef;
+drop table test_data;
+
+drop function bdefault_unsafe;
+drop function cdefault_restricted;
+drop function ddefault_safe;
+drop function fullname_parallel_unsafe;
+drop function fullname_parallel_restricted;
--
2.27.0
Hi,
Based on the discussion in another thread[1]/messages/by-id/CAA4eK1+MQnm6RkqooHA7R-y7riRa84qsh5j3FZDScw71m_n4OA@mail.gmail.com, we plan to change the design like
the following:
allow users to specify a parallel-safety option for both partitioned and
non-partitioned relations but for non-partitioned relations if users didn't
specify, it would be computed automatically? If the user has specified
parallel-safety option for non-partitioned relation then we would consider that
instead of computing the value by ourselves.
In this approach, it will be more convenient for users to use and get the
benefit of parallel select for insert.
Since most of the technical discussion happened in another thread, I
posted the new version patch including the new design to that thread[2]/messages/by-id/OS0PR01MB5716DB1E3F723F86314D080094F09@OS0PR01MB5716.jpnprd01.prod.outlook.com.
Comments are also welcome in that thread.
[1]: /messages/by-id/CAA4eK1+MQnm6RkqooHA7R-y7riRa84qsh5j3FZDScw71m_n4OA@mail.gmail.com
[2]: /messages/by-id/OS0PR01MB5716DB1E3F723F86314D080094F09@OS0PR01MB5716.jpnprd01.prod.outlook.com
Best regards,
houzj