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

