From 09cea1fc46bc97785b3f46932b76c92f17020fdb Mon Sep 17 00:00:00 2001
From: Ajin Cherian <ajinc@fast.au.fujitsu.com>
Date: Wed, 7 Jun 2023 08:07:29 -0400
Subject: [PATCH 5/8] DDL replication for Table DDL commands

OVERVIEW
--------
To support DDL replication, we use the event trigger and DDL deparsing
facilities. During CREATE PUBLICATION we register a 'command end' trigger that
deparses the DDL (if the DDL is annotated as ddlreplok for DDL replication in
cmdtaglist.h) as a JSON blob, and WAL logs it. The event trigger is automatically
removed at the time of DROP PUBLICATION. The WALSender decodes the WAL and sends
it downstream similar to other DML commands. The subscriber then converts JSON
back to the DDL command string and executes it. In the subscriber, we also add
the newly added rel to pg_subscription_rel so that the DML changes on the new
table can be replicated without having to manually run
"ALTER SUBSCRIPTION ... REFRESH PUBLICATION".

For non-rewrite ALTER object command and CREATE object command:
---------------------------------------------------------------
We deparse the command at ddl_command_end event trigger and WAL log the
deparsed JSON string. The WALSender decodes the WAL and sends it to
subscriber if the created/altered table is published.

For DROP object:
----------------
The 'command start' event handler logs a ddl message with the relids of
the tables that are dropped which the output plugin (pgoutput) stores in
its internal data structure after verifying that it is for a table that is
part of the publication. Later the 'command end' event handler sends the
actual drop message. On receiving the 'command end', pgoutput sends the
DROP command only if it is for one of the relids marked for deletion.

The reason we have to do this is because, once the logical decoder
receives the 'command end' message,  the relid of the table is no longer
valid as it has been deleted as part of invalidations received for the
drop table command. It is no longer possible to verify if the table is
part of the publication list or not. To make this possible, two more elements
are added to the ddl xlog and ddl message, (relid and cmdtype).

We could have also handled all this on the subscriber side, but
that would mean sending spurious ddl messages for tables that are not part
of the publication.

For table_rewrite ALTER TABLE command:
--------------------------------------
Executing a non-immutable expression during the table rewrite phase is not
allowed, as it may result in different data between publisher and subscriber.
While some may suggest converting the rewrite inserts to updates and replicate
them afte the ddl command to maintain data consistency. But it doesn't work if
the replica identity column is altered in the command. This is because the
rewrite inserts do not contain the old values and therefore cannot be converted
to update.

LIMIT:
Commands contain volatile functions are not allowed. Because
it's possible the functions contain DDL/DML in which case these operations
will be executed twice and cause duplicate data. In addition, we don't know
whether the tables being accessed by these DDL/DML are published or not. So
blindly allowing such functions can allow unintended clauses like the tables
accessed in those functions may not even exist on the subscriber.
---
 contrib/test_decoding/expected/ddl.out        |  27 ++
 contrib/test_decoding/sql/ddl.sql             |   6 +
 contrib/test_decoding/test_decoding.c         |  48 ++
 doc/src/sgml/catalogs.sgml                    |   9 +
 doc/src/sgml/logical-replication.sgml         |  75 ++++
 src/backend/access/rmgrdesc/Makefile          |   1 +
 .../access/rmgrdesc/logicalddlmsgdesc.c       |  52 +++
 src/backend/access/rmgrdesc/meson.build       |   1 +
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/catalog/pg_publication.c          |   1 +
 src/backend/commands/alter.c                  |  17 +
 src/backend/commands/ddldeparse.c             | 101 ++++-
 src/backend/commands/event_trigger.c          |  60 ++-
 src/backend/commands/publicationcmds.c        | 237 +++++++++-
 src/backend/replication/logical/Makefile      |   2 +
 src/backend/replication/logical/ddlmessage.c  |  83 ++++
 src/backend/replication/logical/ddltrigger.c  | 355 +++++++++++++++
 src/backend/replication/logical/decode.c      |  41 ++
 src/backend/replication/logical/logical.c     |  97 +++-
 .../replication/logical/logicalfuncs.c        |  24 +
 src/backend/replication/logical/meson.build   |   2 +
 src/backend/replication/logical/proto.c       |  43 ++
 .../replication/logical/reorderbuffer.c       | 135 ++++++
 src/backend/replication/logical/worker.c      | 232 ++++++++++
 src/backend/replication/pgoutput/pgoutput.c   | 251 ++++++++---
 src/backend/tcop/cmdtag.c                     |  26 +-
 src/backend/tcop/utility.c                    |   2 +-
 src/backend/utils/cache/relcache.c            |   1 +
 src/bin/pg_dump/pg_dump.c                     |  26 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_dump/t/002_pg_dump.pl              |   2 +-
 src/bin/pg_waldump/.gitignore                 |   1 +
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/psql/describe.c                       |  21 +-
 src/include/access/rmgrlist.h                 |   1 +
 src/include/catalog/pg_event_trigger.h        |   1 +
 src/include/catalog/pg_proc.dat               |  20 +
 src/include/catalog/pg_publication.h          |  30 +-
 src/include/commands/event_trigger.h          |   9 +-
 src/include/commands/publicationcmds.h        |  14 +
 src/include/replication/ddlmessage.h          |  60 +++
 src/include/replication/decode.h              |   1 +
 src/include/replication/logicalproto.h        |   4 +
 src/include/replication/output_plugin.h       |  27 ++
 src/include/replication/pgoutput.h            |   1 +
 src/include/replication/reorderbuffer.h       |  39 ++
 src/include/tcop/cmdtag.h                     |   4 +-
 src/include/tcop/cmdtaglist.h                 | 386 ++++++++--------
 src/include/tcop/ddldeparse.h                 |  13 +-
 src/include/utils/rel.h                       |   2 +
 src/test/regress/expected/psql.out            |   6 +-
 src/test/regress/expected/publication.out     | 420 +++++++++---------
 src/tools/pgindent/typedefs.list              |   6 +
 53 files changed, 2498 insertions(+), 528 deletions(-)
 create mode 100644 src/backend/access/rmgrdesc/logicalddlmsgdesc.c
 create mode 100644 src/backend/replication/logical/ddlmessage.c
 create mode 100644 src/backend/replication/logical/ddltrigger.c
 create mode 100644 src/include/replication/ddlmessage.h

diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index d55fb3a667..c1cf38d4fa 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -831,6 +831,33 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
 ------
 (0 rows)
 
+-- test pg_logical_emit_ddl_message with a practical payload
+SELECT 'ddl msg1' FROM pg_logical_emit_ddl_message('ddl msg1', 16394, 1, 'msg1');
+ ?column? 
+----------
+ ddl msg1
+(1 row)
+
+SELECT 'ddl msg2' FROM pg_logical_emit_ddl_message('ddl msg2', 16394, 1, '{"fmt": "CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", "name": "foo", "authorization": {"fmt": "AUTHORIZATION %{authorization_role}I", "present": false, "authorization_role": null}, "if_not_exists": ""}');
+ ?column? 
+----------
+ ddl msg2
+(1 row)
+
+SELECT 'ddl msg3' FROM pg_logical_emit_ddl_message('ddl msg3', 16394, 1, '{"fmt": "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D (%{table_elements:, }s) %{inherits}s %{tablespace}s %{on_commit}s %{partition_by}s %{access_method}s %{with_clause}s", "identity": {"objname": "foo", "schemaname": "element_test"}, "inherits": {"fmt": "INHERITS (%{parents:, }D)", "parents": null, "present": false}, "on_commit": {"fmt": "ON COMMIT %{on_commit_value}s", "present": false, "on_commit_value": null}, "tablespace": {"fmt": "TABLESPACE %{tablespace}I", "present": false, "tablespace": null}, "persistence": "", "with_clause": {"fmt": "WITH", "present": false}, "partition_by": {"fmt": "PARTITION BY %{definition}s", "present": false, "definition": null}, "access_method": {"fmt": "USING %{access_method}I", "present": false, "access_method": null}, "if_not_exists": "", "table_elements": [{"fmt": "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s %{not_null}s %{default}s %{generated_column}s", "name": "id", "type": "column", "coltype": {"typmod": "", "typarray": false, "typename": "int4", "schemaname": "pg_catalog"}, "default": {"fmt": "DEFAULT", "present": false}, "not_null": "", "collation": {"fmt": "COLLATE", "present": false}, "colstorage": "plain", "compression": {"fmt": "COMPRESSION %{compression_method}I", "present": false, "compression_method": null}, "generated_column": {"fmt": "GENERATED ALWAYS AS", "present": false}}]}}');
+ ?column? 
+----------
+ ddl msg3
+(1 row)
+
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ DDL message: prefix: ddl msg1, relid: 16394, cmdtype: Drop, sz: 4 content: msg1
+ DDL message: prefix: ddl msg2, relid: 16394, cmdtype: Drop, sz: 217 content: {"fmt": "CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", "name": "foo", "authorization": {"fmt": "AUTHORIZATION %{authorization_role}I", "present": false, "authorization_role": null}, "if_not_exists": ""}
+ DDL message: prefix: ddl msg3, relid: 16394, cmdtype: Drop, sz: 1396 content: {"fmt": "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D (%{table_elements:, }s) %{inherits}s %{tablespace}s %{on_commit}s %{partition_by}s %{access_method}s %{with_clause}s", "identity": {"objname": "foo", "schemaname": "element_test"}, "inherits": {"fmt": "INHERITS (%{parents:, }D)", "parents": null, "present": false}, "on_commit": {"fmt": "ON COMMIT %{on_commit_value}s", "present": false, "on_commit_value": null}, "tablespace": {"fmt": "TABLESPACE %{tablespace}I", "present": false, "tablespace": null}, "persistence": "", "with_clause": {"fmt": "WITH", "present": false}, "partition_by": {"fmt": "PARTITION BY %{definition}s", "present": false, "definition": null}, "access_method": {"fmt": "USING %{access_method}I", "present": false, "access_method": null}, "if_not_exists": "", "table_elements": [{"fmt": "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s %{not_null}s %{default}s %{generated_column}s", "name": "id", "type": "column", "coltype": {"typmod": "", "typarray": false, "typename": "int4", "schemaname": "pg_catalog"}, "default": {"fmt": "DEFAULT", "present": false}, "not_null": "", "collation": {"fmt": "COLLATE", "present": false}, "colstorage": "plain", "compression": {"fmt": "COMPRESSION %{compression_method}I", "present": false, "compression_method": null}, "generated_column": {"fmt": "GENERATED ALWAYS AS", "present": false}}]}}
+(3 rows)
+
 SELECT pg_drop_replication_slot('regression_slot');
  pg_drop_replication_slot 
 --------------------------
diff --git a/contrib/test_decoding/sql/ddl.sql b/contrib/test_decoding/sql/ddl.sql
index 57285a828c..592bc89d96 100644
--- a/contrib/test_decoding/sql/ddl.sql
+++ b/contrib/test_decoding/sql/ddl.sql
@@ -431,6 +431,12 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
 -- done, free logical replication slot
 SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
 
+-- test pg_logical_emit_ddl_message with a practical payload
+SELECT 'ddl msg1' FROM pg_logical_emit_ddl_message('ddl msg1', 16394, 1, 'msg1');
+SELECT 'ddl msg2' FROM pg_logical_emit_ddl_message('ddl msg2', 16394, 1, '{"fmt": "CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", "name": "foo", "authorization": {"fmt": "AUTHORIZATION %{authorization_role}I", "present": false, "authorization_role": null}, "if_not_exists": ""}');
+SELECT 'ddl msg3' FROM pg_logical_emit_ddl_message('ddl msg3', 16394, 1, '{"fmt": "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D (%{table_elements:, }s) %{inherits}s %{tablespace}s %{on_commit}s %{partition_by}s %{access_method}s %{with_clause}s", "identity": {"objname": "foo", "schemaname": "element_test"}, "inherits": {"fmt": "INHERITS (%{parents:, }D)", "parents": null, "present": false}, "on_commit": {"fmt": "ON COMMIT %{on_commit_value}s", "present": false, "on_commit_value": null}, "tablespace": {"fmt": "TABLESPACE %{tablespace}I", "present": false, "tablespace": null}, "persistence": "", "with_clause": {"fmt": "WITH", "present": false}, "partition_by": {"fmt": "PARTITION BY %{definition}s", "present": false, "definition": null}, "access_method": {"fmt": "USING %{access_method}I", "present": false, "access_method": null}, "if_not_exists": "", "table_elements": [{"fmt": "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s %{not_null}s %{default}s %{generated_column}s", "name": "id", "type": "column", "coltype": {"typmod": "", "typarray": false, "typename": "int4", "schemaname": "pg_catalog"}, "default": {"fmt": "DEFAULT", "present": false}, "not_null": "", "collation": {"fmt": "COLLATE", "present": false}, "colstorage": "plain", "compression": {"fmt": "COMPRESSION %{compression_method}I", "present": false, "compression_method": null}, "generated_column": {"fmt": "GENERATED ALWAYS AS", "present": false}}]}}');
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+
 SELECT pg_drop_replication_slot('regression_slot');
 
 /* check that the slot is gone */
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index 12d1d0505d..9286f97258 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -14,9 +14,11 @@
 
 #include "catalog/pg_type.h"
 
+#include "replication/ddlmessage.h"
 #include "replication/logical.h"
 #include "replication/origin.h"
 
+#include "tcop/ddldeparse.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -72,6 +74,12 @@ static void pg_decode_message(LogicalDecodingContext *ctx,
 							  ReorderBufferTXN *txn, XLogRecPtr lsn,
 							  bool transactional, const char *prefix,
 							  Size sz, const char *message);
+static void pg_decode_ddl_message(LogicalDecodingContext *ctx,
+								  ReorderBufferTXN *txn,
+								  XLogRecPtr message_lsn,
+								  const char *prefix, Oid relid,
+								  DeparsedCommandType cmdtype,
+								  Size sz, const char *message);
 static bool pg_decode_filter_prepare(LogicalDecodingContext *ctx,
 									 TransactionId xid,
 									 const char *gid);
@@ -135,6 +143,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
 	cb->filter_by_origin_cb = pg_decode_filter;
 	cb->shutdown_cb = pg_decode_shutdown;
 	cb->message_cb = pg_decode_message;
+	cb->ddl_cb = pg_decode_ddl_message;
 	cb->filter_prepare_cb = pg_decode_filter_prepare;
 	cb->begin_prepare_cb = pg_decode_begin_prepare_txn;
 	cb->prepare_cb = pg_decode_prepare_txn;
@@ -750,6 +759,45 @@ pg_decode_message(LogicalDecodingContext *ctx,
 	OutputPluginWrite(ctx, true);
 }
 
+static void
+pg_decode_ddl_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+					  XLogRecPtr message_lsn, const char *prefix, Oid relid,
+					  DeparsedCommandType cmdtype, Size sz, const char *message)
+{
+	OutputPluginPrepareWrite(ctx, true);
+	appendStringInfo(ctx->out, "DDL message: prefix: %s, relid: %u, ",
+					 prefix, relid);
+
+	switch(cmdtype)
+	{
+	case DCT_SimpleCmd:
+		appendStringInfo(ctx->out, "cmdtype: Simple, ");
+		break;
+	case DCT_TableDropStart:
+		appendStringInfo(ctx->out, "cmdtype: Drop start, ");
+		break;
+	case DCT_TableDropEnd:
+		appendStringInfo(ctx->out, "cmdtype: Drop end, ");
+		break;
+	case DCT_TableAlter:
+		appendStringInfo(ctx->out, "cmdtype: Alter table, ");
+		break;
+	case DCT_ObjectCreate:
+		appendStringInfo(ctx->out, "cmdtype: Create, ");
+		break;
+	case DCT_ObjectDrop:
+		appendStringInfo(ctx->out, "cmdtype: Drop, ");
+		break;
+	default:
+		appendStringInfo(ctx->out, "cmdtype: Invalid, ");
+		break;
+	}
+
+	appendStringInfo(ctx->out, "sz: %zu content: ", sz);
+	appendBinaryStringInfo(ctx->out, message, sz);
+	OutputPluginWrite(ctx, true);
+}
+
 static void
 pg_decode_stream_start(LogicalDecodingContext *ctx,
 					   ReorderBufferTXN *txn)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ed32ca0349..3568d070ab 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3863,6 +3863,15 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>evtisinternal</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if the event trigger is internally generated.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>evttags</structfield> <type>text[]</type>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 59cf92e6a9..e9286e9012 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1457,6 +1457,81 @@ test_sub=# SELECT * FROM t1 ORDER BY id;
 
  </sect1>
 
+ <sect1 id="logical-replication-ddl">
+  <title>DDL Replication</title>
+  <para>
+    Data Definition Language (DDL) commands can be replicated using logical replication.
+    When enabled this feature automatically replicates supported DDL commands
+    that are successfully executed on a publisher to a subscriber. This is
+    especially useful if you have many schema changes over time that need replication.
+  </para>
+
+  <para>
+    For example, a CREATE TABLE command executed on the publisher gets
+    WAL-logged, and forwarded to the subscriber to replay; then an implicit "ALTER
+    SUBSCRIPTION ... REFRESH PUBLICATION" is performed on the subscriber database so any
+    following DML changes on the new table can be replicated.
+  </para>
+
+  <para>
+    DDL replication is disabled by default, it can be enabled using the ddl PUBLICATION
+    option. This option currently has one level and is only allowed to be set if the
+    PUBLICATION is FOR ALL TABLES or FOR TABLES IN SCHEMA.
+  </para>
+
+  <itemizedlist>
+    <listitem>
+      <para>
+        table: this option enables replication of Table DDL commands,
+        which include:
+        <itemizedlist>
+          <listitem>
+            <para>
+              CREATE/ALTER/DROP TABLE
+            </para>
+          </listitem>
+
+          <listitem>
+            <para>
+              CREATE TABLE AS
+            </para>
+          </listitem>
+
+        </itemizedlist>
+      </para>
+    </listitem>
+  </itemizedlist>
+
+  <sect2 id="ddl-replication-option-examples">
+    <title>Examples - Setup DDL Replication on the Publisher</title>
+
+    <para>
+      Enable TABLE DDL replication for an existing PUBLICATION:
+<programlisting>
+ALTER PUBLICATION mypub SET (ddl = 'table');
+</programlisting></para>
+
+  </sect2>
+
+  <sect2 id="ddl-replication-supported-commands">
+    <title>Supported DDL commands</title>
+
+    <para>
+      The DDL commands supported for logical replication are Table DDL commands,
+      include <literal>ALTER TABLE</literal>, <literal>CREATE TABLE</literal>,
+      <literal>CREATE TABLE AS</literal>, and <literal>DROP TABLE</literal>.
+      Other object commands and global commands are currently not
+      supported for replication. Global commands can be executed at any
+      database, include ROLE statements, Database statements, TableSpace
+      statements and some of the GrantStmt/RevokeStmt if the target object is a
+      global object. Temporary and unlogged table will not be replicated. User
+      should take care when creating these objects as these objects might be
+      required by the objects that are replicated (for example creation of
+      tables that might refer to an user-created tablespace will fail in the
+      subscriber if the user created tablespaces do not exist on the
+      subscriber).
+    </para>
+
  <sect1 id="logical-replication-conflicts">
   <title>Conflicts</title>
 
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..fd67f044e4 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -18,6 +18,7 @@ OBJS = \
 	gistdesc.o \
 	hashdesc.o \
 	heapdesc.o \
+	logicalddlmsgdesc.o \
 	logicalmsgdesc.o \
 	mxactdesc.o \
 	nbtdesc.o \
diff --git a/src/backend/access/rmgrdesc/logicalddlmsgdesc.c b/src/backend/access/rmgrdesc/logicalddlmsgdesc.c
new file mode 100644
index 0000000000..5f58e0fe51
--- /dev/null
+++ b/src/backend/access/rmgrdesc/logicalddlmsgdesc.c
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * logicalddlmsgdesc.c
+ *	  rmgr descriptor routines for replication/logical/ddlmessage.c
+ *
+ * Portions Copyright (c) 2015-2023, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/rmgrdesc/logicalddlmsgdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "replication/ddlmessage.h"
+
+void
+logicalddlmsg_desc(StringInfo buf, XLogReaderState *record)
+{
+	char	   *rec = XLogRecGetData(record);
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+	if (info == XLOG_LOGICAL_DDL_MESSAGE)
+	{
+		xl_logical_ddl_message *xlrec = (xl_logical_ddl_message *) rec;
+		char	   *prefix = xlrec->message;
+		char	   *message = xlrec->message + xlrec->prefix_size;
+		char	   *sep = "";
+
+		Assert(prefix[xlrec->prefix_size - 1] == '\0');
+
+		appendStringInfo(buf, "prefix \"%s\"; payload (%zu bytes): ",
+						 prefix, xlrec->message_size);
+		appendStringInfo(buf, "relid %u cmdtype %u", xlrec->relid, xlrec->cmdtype);
+		/* Write message payload as a series of hex bytes */
+		for (int cnt = 0; cnt < xlrec->message_size; cnt++)
+		{
+			appendStringInfo(buf, "%s%02X", sep, (unsigned char) message[cnt]);
+			sep = " ";
+		}
+	}
+}
+
+const char *
+logicalddlmsg_identify(uint8 info)
+{
+	if ((info & ~XLR_INFO_MASK) == XLOG_LOGICAL_DDL_MESSAGE)
+		return "DDL";
+
+	return NULL;
+}
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index f76e87e2d7..614fd7c1ac 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -11,6 +11,7 @@ rmgr_desc_sources = files(
   'gistdesc.c',
   'hashdesc.c',
   'heapdesc.c',
+  'logicalddlmsgdesc.c',
   'logicalmsgdesc.c',
   'mxactdesc.c',
   'nbtdesc.c',
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 7d67eda5f7..678e81ae01 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "fmgr.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "replication/ddlmessage.h"
 #include "replication/decode.h"
 #include "replication/message.h"
 #include "replication/origin.h"
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index c488b6370b..d8a5940141 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1028,6 +1028,7 @@ GetPublication(Oid pubid)
 	pub->pubactions.pubupdate = pubform->pubupdate;
 	pub->pubactions.pubdelete = pubform->pubdelete;
 	pub->pubactions.pubtruncate = pubform->pubtruncate;
+	pub->pubactions.pubddl_table = pubform->pubddl_table;
 	pub->pubviaroot = pubform->pubviaroot;
 
 	ReleaseSysCache(tup);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index e95dc31bde..23435c9915 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -308,6 +308,23 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 		/* Wake up related replication workers to handle this change quickly */
 		LogicalRepWorkersWakeupAtCommit(objectId);
 	}
+	else if (classId == EventTriggerRelationId)
+	{
+		Form_pg_event_trigger evtForm = (Form_pg_event_trigger) GETSTRUCT(oldtup);
+
+		if (SearchSysCacheExists1(EVENTTRIGGERNAME, CStringGetDatum(new_name)))
+			report_name_conflict(classId, new_name);
+
+		/*
+		 * Event triggers created internally are not allowed to be altered by
+		 * user.
+		 */
+		if (evtForm->evtisinternal)
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("permission denied: \"%s\" is a system event trigger",
+							NameStr(evtForm->evtname))));
+	}
 	else if (nameCacheId >= 0)
 	{
 		if (OidIsValid(namespaceId))
diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c
index 6799c7fdaf..4eab5ea0c0 100644
--- a/src/backend/commands/ddldeparse.c
+++ b/src/backend/commands/ddldeparse.c
@@ -135,10 +135,10 @@ static ObjElem *new_object_object(ObjTree *value);
 static ObjTree *new_objtree_VA(char *fmt, int numobjs, ...);
 static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
 static char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
-									  List *dpcontext);
+									  List *dpcontext, Node **expr);
 
 static ObjTree *deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
-								  ColumnDef *coldef, bool is_alter);
+								  ColumnDef *coldef, bool is_alter, Node **expr);
 static ObjTree *deparse_ColumnIdentity(Oid seqrelid, char identity, bool alter_table);
 static ObjTree *deparse_ColumnSetOptions(AlterTableCmd *subcmd);
 
@@ -158,6 +158,27 @@ static inline ObjElem *deparse_Seq_As(Form_pg_sequence seqdata);
 static List *deparse_InhRelations(Oid objectId);
 static List *deparse_TableElements(Relation relation, List *tableElements, List *dpcontext,
 								   bool typed, bool composite);
+static void mark_function_volatile(ddl_deparse_context *context, Node *expr);
+
+/*
+ * Mark the max_volatility flag for an expression in the command.
+ */
+static void
+mark_function_volatile(ddl_deparse_context *context, Node *expr)
+{
+	if (context->max_volatility == PROVOLATILE_VOLATILE)
+		return;
+
+	if (contain_volatile_functions(expr))
+	{
+		context->max_volatility = PROVOLATILE_VOLATILE;
+		return;
+	}
+
+	if (context->max_volatility == PROVOLATILE_IMMUTABLE &&
+		contain_mutable_functions(expr))
+		context->max_volatility = PROVOLATILE_STABLE;
+}
 
 /*
  * Append present as false to a tree.
@@ -850,7 +871,8 @@ obtainConstraints(List *elements, Oid relationId)
  * passed attribute has a default value.
  */
 static char *
-RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
+RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext,
+						 Node **expr)
 {
 	Node	   *defval;
 	char	   *defstr;
@@ -860,6 +882,10 @@ RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
 
 	defstr = deparse_expression(defval, dpcontext, false, false);
 
+	/* Collect the expression for later replication safety checks */
+	if (expr)
+		*expr = defval;
+
 	return defstr;
 }
 
@@ -903,7 +929,7 @@ RelationGetPartitionBound(Oid relid)
  */
 static ObjTree *
 deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
-				  ColumnDef *coldef, bool is_alter)
+				  ColumnDef *coldef, bool is_alter, Node **expr)
 {
 	ObjTree    *ret;
 	ObjTree    *tmp_obj;
@@ -1006,7 +1032,7 @@ deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
 			char	   *defstr;
 
 			defstr = RelationGetColumnDefault(relation, attrForm->attnum,
-											  dpcontext);
+											  dpcontext, expr);
 
 			append_string_object(tmp_obj, "%{default}s", "default", defstr);
 		}
@@ -1047,7 +1073,7 @@ deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
 			char	   *defstr;
 
 			defstr = RelationGetColumnDefault(relation, attrForm->attnum,
-											  dpcontext);
+											  dpcontext, expr);
 			append_string_object(tmp_obj, "(%{generation_expr}s) STORED",
 								 "generation_expr", defstr);
 		}
@@ -1125,7 +1151,7 @@ deparse_ColumnDef_typed(Relation relation, List *dpcontext, ColumnDef *coldef)
 		char	   *defstr;
 
 		defstr = RelationGetColumnDefault(relation, attrForm->attnum,
-										  dpcontext);
+										  dpcontext, NULL);
 
 		append_string_object(tmp_obj, "%{default}s", "default", defstr);
 	}
@@ -1669,7 +1695,7 @@ deparse_TableElements(Relation relation, List *tableElements, List *dpcontext,
 												(ColumnDef *) elt) :
 						deparse_ColumnDef(relation, dpcontext,
 										  composite, (ColumnDef *) elt,
-										  false);
+										  false, NULL);
 					if (tree != NULL)
 						elements = lappend(elements, new_object_object(tree));
 				}
@@ -1970,7 +1996,7 @@ deparse_CreateTableAsStmt(CollectedCommand *cmd)
  * ALTER %{objtype}s %{only}s %{identity}D %{subcmds:, }s
  */
 static ObjTree *
-deparse_AlterRelation(CollectedCommand *cmd)
+deparse_AlterRelation(CollectedCommand *cmd, ddl_deparse_context *context)
 {
 	ObjTree    *ret;
 	ObjTree    *tmp_obj;
@@ -1980,6 +2006,7 @@ deparse_AlterRelation(CollectedCommand *cmd)
 	List	   *subcmds = NIL;
 	ListCell   *cell;
 	const char *reltype;
+	Node	   *expr = NULL;
 	Oid			relId = cmd->d.alterTable.objectId;
 	AlterTableStmt *stmt = NULL;
 
@@ -2052,7 +2079,10 @@ deparse_AlterRelation(CollectedCommand *cmd)
 				/* XXX need to set the "recurse" bit somewhere? */
 				Assert(IsA(subcmd->def, ColumnDef));
 				tree = deparse_ColumnDef(rel, dpcontext, false,
-										 (ColumnDef *) subcmd->def, true);
+										 (ColumnDef *) subcmd->def, true, &expr);
+
+				mark_function_volatile(context, expr);
+
 				tmp_obj = new_objtree_VA("ADD %{objtype}s %{if_not_exists}s %{definition}s", 4,
 										"objtype", ObjTypeString, "COLUMN",
 										"type", ObjTypeString, "add column",
@@ -2162,7 +2192,8 @@ deparse_AlterRelation(CollectedCommand *cmd)
 					attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
 					append_string_object(tmp_obj, "%{definition}s", "definition",
 										 RelationGetColumnDefault(rel, attno,
-																  dpcontext_rel));
+																  dpcontext_rel,
+																  NULL));
 					ReleaseSysCache(attrtup);
 				}
 
@@ -2297,12 +2328,40 @@ deparse_AlterRelation(CollectedCommand *cmd)
 				{
 					/* XXX need to set the "recurse" bit somewhere? */
 					Oid			constrOid = sub->address.objectId;
+					bool		isnull;
+					HeapTuple	tup;
+					Datum		val;
+					Constraint *constr;
 
 					/* Skip adding constraint for inherits table sub command */
 					if (!constrOid)
 						continue;
 
 					Assert(IsA(subcmd->def, Constraint));
+					constr = castNode(Constraint, subcmd->def);
+
+					if (!constr->skip_validation)
+					{
+						tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
+
+						if (HeapTupleIsValid(tup))
+						{
+							char	   *conbin;
+
+							/* Fetch constraint expression in parsetree form */
+							val = SysCacheGetAttr(CONSTROID, tup,
+												  Anum_pg_constraint_conbin, &isnull);
+
+							if (!isnull)
+							{
+								conbin = TextDatumGetCString(val);
+								expr = stringToNode(conbin);
+								mark_function_volatile(context, expr);
+							}
+
+							ReleaseSysCache(tup);
+						}
+					}
 
 					tmp_obj = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", 3,
 											"type", ObjTypeString, "add constraint",
@@ -2392,8 +2451,11 @@ deparse_AlterRelation(CollectedCommand *cmd)
 					 */
 					tmp_obj2 = new_objtree("USING");
 					if (def->raw_default)
+					{
 						append_string_object(tmp_obj2, "%{expression}s",
 											 "expression", sub->usingexpr);
+						mark_function_volatile(context, def->cooked_default);
+					}
 					else
 						append_not_present(tmp_obj2, "%{expression}s");
 
@@ -3113,7 +3175,7 @@ deparse_simple_command(CollectedCommand *cmd)
  * Workhorse to deparse a CollectedCommand.
  */
 char *
-deparse_utility_command(CollectedCommand *cmd, bool verbose_mode)
+deparse_utility_command(CollectedCommand *cmd, ddl_deparse_context *context)
 {
 	OverrideSearchPath *overridePath;
 	MemoryContext oldcxt;
@@ -3149,7 +3211,7 @@ deparse_utility_command(CollectedCommand *cmd, bool verbose_mode)
 	overridePath->addTemp = true;
 	PushOverrideSearchPath(overridePath);
 
-	verbose = verbose_mode;
+	verbose = context->verbose_mode;
 
 	switch (cmd->type)
 	{
@@ -3157,7 +3219,7 @@ deparse_utility_command(CollectedCommand *cmd, bool verbose_mode)
 			tree = deparse_simple_command(cmd);
 			break;
 		case SCT_AlterTable:
-			tree = deparse_AlterRelation(cmd);
+			tree = deparse_AlterRelation(cmd, context);
 			break;
 		case SCT_CreateTableAs:
 			tree = deparse_CreateTableAsStmt(cmd);
@@ -3197,8 +3259,17 @@ ddl_deparse_to_json(PG_FUNCTION_ARGS)
 {
 	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
 	char	   *command;
+	ddl_deparse_context context;
+
+	context.verbose_mode = true;
+
+	/*
+	 * Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
+	 * the minimum volatility level.
+	 */
+	context.max_volatility = PROVOLATILE_IMMUTABLE;
 
-	command = deparse_utility_command(cmd, true);
+	command = deparse_utility_command(cmd, &context);
 
 	if (command)
 		PG_RETURN_TEXT_P(cstring_to_text(command));
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 8561c6fad0..605b73709d 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -58,7 +58,7 @@ static void AlterEventTriggerOwner_internal(Relation rel,
 static void error_duplicate_filter_variable(const char *defname);
 static Datum filter_list_to_array(List *filterlist);
 static Oid	insert_event_trigger_tuple(const char *trigname, const char *eventname,
-									   Oid evtOwner, Oid funcoid, List *taglist);
+									   Oid evtOwner, Oid funcoid, List *taglist, bool is_internal);
 static void validate_ddl_tags(const char *filtervar, List *taglist);
 static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
 static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
@@ -69,7 +69,7 @@ static const char *stringify_adefprivs_objtype(ObjectType objtype);
  * Create an event trigger.
  */
 Oid
-CreateEventTrigger(CreateEventTrigStmt *stmt)
+CreateEventTrigger(CreateEventTrigStmt *stmt, bool is_internal)
 {
 	HeapTuple	tuple;
 	Oid			funcoid;
@@ -91,11 +91,11 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
 				 errhint("Must be superuser to create an event trigger.")));
 
 	/* Validate event name. */
-	if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
-		strcmp(stmt->eventname, "ddl_command_end") != 0 &&
-		strcmp(stmt->eventname, "sql_drop") != 0 &&
-		strcmp(stmt->eventname, "table_rewrite") != 0 &&
-		strcmp(stmt->eventname, "table_init_write") != 0)
+	if (strcmp(stmt->eventname, TRIG_DDL_CMD_START) != 0 &&
+		strcmp(stmt->eventname, TRIG_DDL_CMD_END) != 0 &&
+		strcmp(stmt->eventname, TRIG_TBL_CMD_DROP) != 0 &&
+		strcmp(stmt->eventname, TRIG_TBL_REWRITE) != 0 &&
+		strcmp(stmt->eventname, TRIG_TBL_INIT_WRITE) != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("unrecognized event name \"%s\"",
@@ -119,13 +119,13 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
 	}
 
 	/* Validate tag list, if any. */
-	if ((strcmp(stmt->eventname, "ddl_command_start") == 0 ||
-		 strcmp(stmt->eventname, "ddl_command_end") == 0 ||
-		 strcmp(stmt->eventname, "sql_drop") == 0 ||
-		 strcmp(stmt->eventname, "table_init_write") == 0)
+	if ((strcmp(stmt->eventname, TRIG_DDL_CMD_START) == 0 ||
+		 strcmp(stmt->eventname, TRIG_DDL_CMD_START) == 0 ||
+		 strcmp(stmt->eventname, TRIG_TBL_CMD_DROP) == 0 ||
+		 strcmp(stmt->eventname, TRIG_TBL_INIT_WRITE) == 0)
 		&& tags != NULL)
 		validate_ddl_tags("tag", tags);
-	else if (strcmp(stmt->eventname, "table_rewrite") == 0
+	else if (strcmp(stmt->eventname, TRIG_TBL_REWRITE) == 0
 			 && tags != NULL)
 		validate_table_rewrite_tags("tag", tags);
 
@@ -151,7 +151,7 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
 
 	/* Insert catalog entries. */
 	return insert_event_trigger_tuple(stmt->trigname, stmt->eventname,
-									  evtowner, funcoid, tags);
+									  evtowner, funcoid, tags, is_internal);
 }
 
 /*
@@ -220,7 +220,7 @@ error_duplicate_filter_variable(const char *defname)
  */
 static Oid
 insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtOwner,
-						   Oid funcoid, List *taglist)
+						   Oid funcoid, List *taglist, bool is_internal)
 {
 	Relation	tgrel;
 	Oid			trigoid;
@@ -248,6 +248,7 @@ insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtO
 	values[Anum_pg_event_trigger_evtfoid - 1] = ObjectIdGetDatum(funcoid);
 	values[Anum_pg_event_trigger_evtenabled - 1] =
 		CharGetDatum(TRIGGER_FIRES_ON_ORIGIN);
+	values[Anum_pg_event_trigger_evtisinternal - 1] = BoolGetDatum(is_internal);
 	if (taglist == NIL)
 		nulls[Anum_pg_event_trigger_evttags - 1] = true;
 	else
@@ -526,6 +527,7 @@ EventTriggerCommonSetup(Node *parsetree,
 	List	   *cachelist;
 	ListCell   *lc;
 	List	   *runlist = NIL;
+	int			pub_deparse_func_cnt = 0;
 
 	/*
 	 * We want the list of command tags for which this procedure is actually
@@ -576,6 +578,12 @@ EventTriggerCommonSetup(Node *parsetree,
 	 * once we do anything at all that touches the catalogs, an invalidation
 	 * might leave cachelist pointing at garbage, so we must do this before we
 	 * can do much else.
+	 *
+	 * Special handling for event triggers created as part of publications.
+	 * If there are multiple publications which publish ddls, only one set of the
+	 * event trigger functions need to be invoked. The ddl deparse event triggers
+	 * write to WAL, so no need to duplicate it as all walsenders will read the same
+	 * WAL.
 	 */
 	foreach(lc, cachelist)
 	{
@@ -583,8 +591,26 @@ EventTriggerCommonSetup(Node *parsetree,
 
 		if (filter_event_trigger(tag, item))
 		{
-			/* We must plan to fire this trigger. */
-			runlist = lappend_oid(runlist, item->fnoid);
+			static const char *trigger_func_prefix = "publication_deparse_%s";
+			char		trigger_func_name[NAMEDATALEN];
+			Oid			pub_funcoid;
+			List 		*pub_funcname;
+
+			/* Get function oid of the publication's ddl deparse event trigger */
+			snprintf(trigger_func_name, sizeof(trigger_func_name), trigger_func_prefix,
+					 eventstr);
+			pub_funcname = SystemFuncName(trigger_func_name);
+			pub_funcoid = LookupFuncName(pub_funcname, 0, NULL, true);
+
+			if (item->fnoid == pub_funcoid)
+			{
+				/* Only the first ddl deparse event trigger needs to be invoked */
+				if (pub_deparse_func_cnt++ == 0)
+					runlist = lappend_oid(runlist, item->fnoid);
+			}
+			else
+				runlist = lappend_oid(runlist, item->fnoid);
+
 		}
 	}
 
@@ -630,7 +656,7 @@ EventTriggerDDLCommandStart(Node *parsetree)
 
 	runlist = EventTriggerCommonSetup(parsetree,
 									  EVT_DDLCommandStart,
-									  "ddl_command_start",
+									  TRIG_DDL_CMD_START,
 									  &trigdata);
 	if (runlist == NIL)
 		return;
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index f4ba572697..2e28d25f62 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -38,10 +38,12 @@
 #include "commands/publicationcmds.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_relation.h"
+#include "parser/parser.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/array.h"
@@ -85,18 +87,21 @@ parse_publication_options(ParseState *pstate,
 						  bool *publish_given,
 						  PublicationActions *pubactions,
 						  bool *publish_via_partition_root_given,
-						  bool *publish_via_partition_root)
+						  bool *publish_via_partition_root,
+						  bool *ddl_type_given)
 {
 	ListCell   *lc;
 
 	*publish_given = false;
 	*publish_via_partition_root_given = false;
+	*ddl_type_given = false;
 
 	/* defaults */
 	pubactions->pubinsert = true;
 	pubactions->pubupdate = true;
 	pubactions->pubdelete = true;
 	pubactions->pubtruncate = true;
+	pubactions->pubddl_table = false;
 	*publish_via_partition_root = false;
 
 	/* Parse options */
@@ -158,6 +163,42 @@ parse_publication_options(ParseState *pstate,
 			*publish_via_partition_root_given = true;
 			*publish_via_partition_root = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "ddl") == 0)
+		{
+			char	   *ddl_types;
+			List	   *ddl_type_list;
+			ListCell   *lc2;
+
+			if (*ddl_type_given)
+				errorConflictingDefElem(defel, pstate);
+
+			/*
+			 * If ddl option was given only the explicitly listed ddl types
+			 * should be published.
+			 */
+			pubactions->pubddl_table = false;
+
+			*ddl_type_given = true;
+			ddl_types = defGetString(defel);
+
+			if (!SplitIdentifierString(ddl_types, ',', &ddl_type_list))
+				ereport(ERROR,
+						errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("invalid list syntax for \"ddl\" option"));
+
+			/* Process the option list. */
+			foreach(lc2, ddl_type_list)
+			{
+				char	   *ddl_opt = (char *) lfirst(lc2);
+
+				if (strcmp(ddl_opt, "table") == 0)
+					pubactions->pubddl_table = true;
+				else
+					ereport(ERROR,
+							errcode(ERRCODE_SYNTAX_ERROR),
+							errmsg("unrecognized \"ddl\" value: \"%s\"", ddl_opt));
+			}
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -728,6 +769,139 @@ CheckPubRelationColumnList(char *pubname, List *tables,
 	}
 }
 
+/*
+ * Helper function to create an event trigger for DDL replication.
+ */
+static void
+CreateDDLReplicaEventTrigger(char *eventname, List *commands, Oid puboid)
+{
+	List	   *tags = NIL;
+	ListCell   *lc;
+	Oid			trigger_id;
+	ObjectAddress referenced;
+	ObjectAddress pubaddress;
+	CreateEventTrigStmt *ddl_trigger;
+	char		trigger_name[NAMEDATALEN];
+	char		trigger_func_name[NAMEDATALEN];
+	static const char *trigger_func_format = "publication_deparse_%s";
+
+	ddl_trigger = makeNode(CreateEventTrigStmt);
+
+	snprintf(trigger_name, sizeof(trigger_name), PUB_EVENT_TRIG_FORMAT,
+			 eventname, puboid);
+	snprintf(trigger_func_name, sizeof(trigger_func_name), trigger_func_format,
+			 eventname);
+
+	ddl_trigger->trigname = pstrdup(trigger_name);
+	ddl_trigger->eventname = eventname;
+	ddl_trigger->funcname = SystemFuncName(trigger_func_name);
+
+	foreach(lc, commands)
+	{
+		CommandTag cmdtag = lfirst_int(lc);
+		String	   *tag = makeString(pstrdup(GetCommandTagName(cmdtag)));
+
+		tags = lappend(tags, tag);
+	}
+
+	ddl_trigger->whenclause = list_make1(makeDefElem("tag", (Node *) tags, -1));
+
+	trigger_id = CreateEventTrigger(ddl_trigger, true);
+
+	ObjectAddressSet(pubaddress, PublicationRelationId, puboid);
+
+	/*
+	 * Register the event trigger as internally dependent on the publication.
+	 */
+	ObjectAddressSet(referenced, EventTriggerRelationId, trigger_id);
+	recordDependencyOn(&referenced, &pubaddress, DEPENDENCY_INTERNAL);
+}
+
+/*
+ * If DDL replication is enabled, create event triggers to capture and log any
+ * relevant events.
+ */
+static void
+CreateDDLReplicaEventTriggers(PublicationActions pubactions, Oid puboid)
+{
+	List	   *init_commands = NIL;
+	List	   *start_commands = NIL;
+	List	   *rewrite_commands = NIL;
+	List	   *end_commands = NIL;
+
+	if (!pubactions.pubddl_table)
+		return;
+
+	init_commands = lappend_int(init_commands, CMDTAG_CREATE_TABLE_AS);
+	init_commands = lappend_int(init_commands, CMDTAG_SELECT_INTO);
+
+	start_commands = lappend_int(start_commands, CMDTAG_DROP_TABLE);
+	rewrite_commands = lappend_int(rewrite_commands, CMDTAG_ALTER_TABLE);
+
+	end_commands = lappend_int(end_commands, CMDTAG_CREATE_TABLE);
+	end_commands = lappend_int(end_commands, CMDTAG_ALTER_TABLE);
+	end_commands = lappend_int(end_commands, CMDTAG_DROP_TABLE);
+
+	/* Create the table_init_write event trigger */
+	if (init_commands != NIL)
+		CreateDDLReplicaEventTrigger(PUB_TRIG_TBL_INIT_WRITE, init_commands, puboid);
+
+	/* Create the ddl_command_start event trigger */
+	if (start_commands != NIL)
+		CreateDDLReplicaEventTrigger(PUB_TRIG_DDL_CMD_START, start_commands, puboid);
+
+	/* Create the table_rewrite event trigger */
+	if (rewrite_commands != NIL)
+		CreateDDLReplicaEventTrigger(PUB_TRIG_TBL_REWRITE, rewrite_commands, puboid);
+
+	/* Create the ddl_command_end event trigger */
+	if (end_commands != NIL)
+		CreateDDLReplicaEventTrigger(PUB_TRIG_DDL_CMD_END, end_commands, puboid);
+}
+
+/*
+ * Helper function to drop an event trigger for DDL replication.
+ */
+static void
+DropDDLReplicaEventTrigger(char *eventname, Oid puboid)
+{
+	char			trigger_name[NAMEDATALEN];
+	Oid				evtoid;
+	ObjectAddress	obj;
+
+	snprintf(trigger_name, sizeof(trigger_name), PUB_EVENT_TRIG_FORMAT,
+			 eventname, puboid);
+
+	evtoid = get_event_trigger_oid(trigger_name, true);
+	if (!OidIsValid(evtoid))
+		return;
+
+	deleteDependencyRecordsForClass(EventTriggerRelationId, evtoid,
+									PublicationRelationId,
+									DEPENDENCY_INTERNAL);
+
+	/*
+	 * Ensure that the dependency removal is visible, so that we can drop the
+	 * event trigger.
+	 */
+	CommandCounterIncrement();
+
+	ObjectAddressSet(obj, EventTriggerRelationId, evtoid);
+	performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+}
+
+/*
+ * Drop all the event triggers which are used for DDL replication.
+ */
+static void
+DropDDLReplicaEventTriggers(Oid puboid)
+{
+	DropDDLReplicaEventTrigger(TRIG_DDL_CMD_START, puboid);
+	DropDDLReplicaEventTrigger(TRIG_DDL_CMD_END, puboid);
+	DropDDLReplicaEventTrigger(TRIG_TBL_REWRITE, puboid);
+	DropDDLReplicaEventTrigger(TRIG_TBL_INIT_WRITE, puboid);
+}
+
 /*
  * Create new publication.
  */
@@ -741,6 +915,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
 	Datum		values[Natts_pg_publication];
 	HeapTuple	tup;
 	bool		publish_given;
+	bool		ddl_type_given;
 	PublicationActions pubactions;
 	bool		publish_via_partition_root_given;
 	bool		publish_via_partition_root;
@@ -783,7 +958,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
 							  stmt->options,
 							  &publish_given, &pubactions,
 							  &publish_via_partition_root_given,
-							  &publish_via_partition_root);
+							  &publish_via_partition_root,
+							  &ddl_type_given);
 
 	puboid = GetNewOidWithIndex(rel, PublicationObjectIndexId,
 								Anum_pg_publication_oid);
@@ -798,6 +974,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
 		BoolGetDatum(pubactions.pubdelete);
 	values[Anum_pg_publication_pubtruncate - 1] =
 		BoolGetDatum(pubactions.pubtruncate);
+	values[Anum_pg_publication_pubddl_table - 1] =
+	BoolGetDatum(pubactions.pubddl_table);
 	values[Anum_pg_publication_pubviaroot - 1] =
 		BoolGetDatum(publish_via_partition_root);
 
@@ -835,6 +1013,11 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
 		{
 			List	   *rels;
 
+			if (pubactions.pubddl_table)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("DDL replication is only supported in FOR ALL TABLES or FOR TABLES IN SCHEMA publications"));
+
 			rels = OpenTableList(relations);
 			TransformPubWhereClauses(rels, pstate->p_sourcetext,
 									 publish_via_partition_root);
@@ -858,6 +1041,11 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
 		}
 	}
 
+	/*
+	 * Create event triggers to allow logging of DDL statements.
+	 */
+	CreateDDLReplicaEventTriggers(pubactions, puboid);
+
 	table_close(rel, RowExclusiveLock);
 
 	InvokeObjectPostCreateHook(PublicationRelationId, puboid, 0);
@@ -882,6 +1070,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
 	bool		replaces[Natts_pg_publication];
 	Datum		values[Natts_pg_publication];
 	bool		publish_given;
+	bool		ddl_type_given;
 	PublicationActions pubactions;
 	bool		publish_via_partition_root_given;
 	bool		publish_via_partition_root;
@@ -890,13 +1079,14 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
 	List	   *root_relids = NIL;
 	ListCell   *lc;
 
+	pubform = (Form_pg_publication) GETSTRUCT(tup);
+
 	parse_publication_options(pstate,
 							  stmt->options,
 							  &publish_given, &pubactions,
 							  &publish_via_partition_root_given,
-							  &publish_via_partition_root);
-
-	pubform = (Form_pg_publication) GETSTRUCT(tup);
+							  &publish_via_partition_root,
+							  &ddl_type_given);
 
 	/*
 	 * If the publication doesn't publish changes via the root partitioned
@@ -978,6 +1168,18 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
 		}
 	}
 
+	if (ddl_type_given && pubactions.pubddl_table)
+	{
+		if (root_relids == NIL)
+			root_relids = GetPublicationRelations(pubform->oid,
+												  PUBLICATION_PART_ROOT);
+
+		if (root_relids)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("DDL replication is only supported in FOR ALL TABLES or FOR TABLES IN SCHEMA publications"));
+	}
+
 	/* Everything ok, form a new tuple. */
 	memset(values, 0, sizeof(values));
 	memset(nulls, false, sizeof(nulls));
@@ -998,6 +1200,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
 		replaces[Anum_pg_publication_pubtruncate - 1] = true;
 	}
 
+	if (ddl_type_given)
+	{
+		/* Recreate the event triggers if the ddl option is changed. */
+		if (pubform->pubddl_table != pubactions.pubddl_table)
+		{
+			DropDDLReplicaEventTriggers(pubform->oid);
+			CreateDDLReplicaEventTriggers(pubactions, pubform->oid);
+		}
+
+		values[Anum_pg_publication_pubddl_table - 1] = BoolGetDatum(pubactions.pubddl_table);
+		replaces[Anum_pg_publication_pubddl_table - 1] = true;
+	}
+
 	if (publish_via_partition_root_given)
 	{
 		values[Anum_pg_publication_pubviaroot - 1] = BoolGetDatum(publish_via_partition_root);
@@ -1103,6 +1318,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
 
 	if (stmt->action == AP_AddObjects)
 	{
+		if (pubform->pubddl_table)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
+						   stmt->pubname));
+
 		TransformPubWhereClauses(rels, queryString, pubform->pubviaroot);
 
 		publish_schema |= is_schema_publication(pubid);
@@ -1121,6 +1342,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
 		List	   *delrels = NIL;
 		ListCell   *oldlc;
 
+		if (pubform->pubddl_table)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
+						   stmt->pubname));
+
 		TransformPubWhereClauses(rels, queryString, pubform->pubviaroot);
 
 		CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..b79ddd8cdc 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -16,6 +16,8 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 
 OBJS = \
 	applyparallelworker.o \
+	ddlmessage.o \
+	ddltrigger.o \
 	decode.o \
 	launcher.o \
 	logical.o \
diff --git a/src/backend/replication/logical/ddlmessage.c b/src/backend/replication/logical/ddlmessage.c
new file mode 100644
index 0000000000..3f144de395
--- /dev/null
+++ b/src/backend/replication/logical/ddlmessage.c
@@ -0,0 +1,83 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddlmessage.c
+ *	  Logical DDL messages.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/ddlmessage.c
+ *
+ * NOTES
+ *
+ * Logical DDL messages allow XLOG logging of DDL command strings that
+ * get passed to the logical decoding plugin. In normal XLOG processing they
+ * are same as NOOP.
+ *
+ * Unlike generic logical messages, these DDL messages have only transactional
+ * mode. Note by default DDLs in PostgreSQL are transactional.
+ *
+ * These messages are part of current transaction and will be sent to
+ * decoding plugin similar to DML operations.
+ *
+ * Every message includes a prefix to avoid conflicts between different decoding
+ * plugins. Plugin authors must take special care to use a unique prefix (e.g one
+ * idea is to include the name of the extension).
+ * ---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "access/xloginsert.h"
+#include "catalog/namespace.h"
+#include "miscadmin.h"
+#include "nodes/execnodes.h"
+#include "replication/logical.h"
+#include "replication/ddlmessage.h"
+#include "utils/memutils.h"
+
+/*
+ * Write logical decoding DDL message into XLog.
+ */
+XLogRecPtr
+LogLogicalDDLMessage(const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+					 const char *message, size_t size)
+{
+	xl_logical_ddl_message xlrec;
+
+	 /* Ensure we have a valid transaction id. */
+	Assert(IsTransactionState());
+	GetCurrentTransactionId();
+
+	xlrec.dbId = MyDatabaseId;
+	/* Trailing zero is critical; see logicalddlmsg_desc */
+	xlrec.prefix_size = strlen(prefix) + 1;
+	xlrec.message_size = size;
+	xlrec.relid = relid;
+	xlrec.cmdtype = cmdtype;
+
+	XLogBeginInsert();
+	XLogRegisterData((char *) &xlrec, SizeOfLogicalDDLMessage);
+	XLogRegisterData(unconstify(char *, prefix), xlrec.prefix_size);
+	XLogRegisterData(unconstify(char *, message), size);
+
+	/* Allow origin filtering */
+	XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+	return XLogInsert(RM_LOGICALDDLMSG_ID, XLOG_LOGICAL_DDL_MESSAGE);
+}
+
+/*
+ * Redo is basically just noop for logical decoding DDL messages.
+ */
+void
+logicalddlmsg_redo(XLogReaderState *record)
+{
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+	if (info != XLOG_LOGICAL_DDL_MESSAGE)
+		elog(PANIC, "logicalddlmsg_redo: unknown op code %u", info);
+
+	/* This is only interesting for logical decoding, see decode.c. */
+}
diff --git a/src/backend/replication/logical/ddltrigger.c b/src/backend/replication/logical/ddltrigger.c
new file mode 100644
index 0000000000..7147a567c6
--- /dev/null
+++ b/src/backend/replication/logical/ddltrigger.c
@@ -0,0 +1,355 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddltrigger.c
+ *	  Logical DDL triggers.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/ddltrigger.c
+ *
+ * NOTES
+ *
+ * Deparse the ddl command and log it.
+ *
+ * ---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "funcapi.h"
+#include "lib/ilist.h"
+#include "replication/ddlmessage.h"
+#include "tcop/ddldeparse.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+
+extern EventTriggerQueryState *currentEventTriggerState;
+
+
+/*
+ * Check if the command can be published.
+ *
+ * XXX Executing a non-immutable expression during the table rewrite phase is
+ * not allowed, as it may result in different data between publisher and
+ * subscriber. While some may suggest converting the rewrite inserts to updates
+ * and replicate them after the ddl command to maintain data consistency, but it
+ * doesn't work if the replica identity column is altered in the command. This
+ * is because the rewrite inserts do not contain the old values and therefore
+ * cannot be converted to update.
+ *
+ * Apart from that, commands containing volatile functions are not allowed. Because
+ * it's possible the functions contain DDL/DML in which case these operations
+ * will be executed twice and cause duplicate data. In addition, we don't know
+ * whether the tables being accessed by these DDL/DML are published or not. So
+ * blindly allowing such functions can allow unintended clauses like the tables
+ * accessed in those functions may not even exist on the subscriber.
+ */
+static void
+check_command_publishable(ddl_deparse_context context, bool is_rewrite)
+{
+
+	if (is_rewrite && context.max_volatility != PROVOLATILE_IMMUTABLE)
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("cannot rewrite table if this command contains mutable function because it cannot be replicated in DDL replication"));
+
+	if (context.max_volatility == PROVOLATILE_VOLATILE)
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("cannot use volatile function in this command because it cannot be replicated in DDL replication"));
+}
+
+/*
+ * Deparse the ddl command and log it prior to
+ * execution. Currently only used for DROP TABLE command
+ * so that catalog can be accessed before being deleted.
+ * This is to check if the table is part of the publication
+ * or not.
+ */
+Datum
+publication_deparse_ddl_command_start(PG_FUNCTION_ARGS)
+{
+	EventTriggerData *trigdata;
+	char	   *command = psprintf("Drop table command start");
+	DropStmt   *stmt;
+	ListCell   *cell1;
+
+	if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+		elog(ERROR, "not fired by event trigger manager");
+
+	trigdata = (EventTriggerData *) fcinfo->context;
+	stmt = (DropStmt *) trigdata->parsetree;
+
+	/* Extract the relid from the parse tree */
+	foreach(cell1, stmt->objects)
+	{
+		char		relpersist;
+		Node	   *object = lfirst(cell1);
+		ObjectAddress address;
+		Relation	relation = NULL;
+
+		address = get_object_address(stmt->removeType,
+									 object,
+									 &relation,
+									 AccessExclusiveLock,
+									 true);
+
+		/* Object does not exist, nothing to do */
+		if (!relation)
+			continue;
+
+		relpersist = get_rel_persistence(address.objectId);
+
+		/*
+		 * Do not generate wal log for commands whose target table is a
+		 * temporary or unlogged table.
+		 *
+		 * XXX We may generate wal logs for unlogged tables in the future so
+		 * that unlogged tables can also be created and altered on the
+		 * subscriber side. This makes it possible to directly replay the SET
+		 * LOGGED command and the incoming rewrite message without creating a
+		 * new table.
+		 */
+		if (relpersist == RELPERSISTENCE_PERMANENT)
+			LogLogicalDDLMessage("deparse", address.objectId, DCT_TableDropStart,
+								 command, strlen(command) + 1);
+
+		table_close(relation, NoLock);
+	}
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * publication_deparse_table_rewrite
+ *
+ * Deparse the ddl table rewrite command and log it.
+ */
+Datum
+publication_deparse_table_rewrite(PG_FUNCTION_ARGS)
+{
+	char		relpersist;
+	CollectedCommand *cmd;
+
+	if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+		elog(ERROR, "not fired by event trigger manager");
+
+	cmd = currentEventTriggerState->currentCommand;
+
+	Assert(cmd && cmd->d.alterTable.rewrite);
+
+	relpersist = get_rel_persistence(cmd->d.alterTable.objectId);
+
+	/*
+	 * Do not generate wal log for commands whose target table is a temporary
+	 * or unlogged table.
+	 *
+	 * XXX We may generate wal logs for unlogged tables in the future so that
+	 * unlogged tables can also be created and altered on the subscriber side.
+	 * This makes it possible to directly replay the SET LOGGED command and the
+	 * incoming rewrite message without creating a new table.
+	 */
+	if (relpersist == RELPERSISTENCE_PERMANENT)
+	{
+		ddl_deparse_context context;
+		char	   *json_string;
+
+		context.verbose_mode = false;
+
+		/*
+		 * Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
+		 * the minimum volatility level.
+		 */
+		context.max_volatility = PROVOLATILE_IMMUTABLE;
+
+		/* Deparse the DDL command and WAL log it to allow decoding of the same. */
+		json_string = deparse_utility_command(cmd, &context);
+
+		if (json_string != NULL)
+		{
+			check_command_publishable(context, true);
+			LogLogicalDDLMessage("deparse", cmd->d.alterTable.objectId, DCT_TableAlter,
+								 json_string, strlen(json_string) + 1);
+		}
+	}
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * Deparse the ddl command and log it. This function
+ * is called after the execution of the command but before the
+ * transaction commits.
+ */
+Datum
+publication_deparse_ddl_command_end(PG_FUNCTION_ARGS)
+{
+	ListCell   *lc;
+	slist_iter	iter;
+	Oid			relid;
+	char		relkind;
+
+	if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+		elog(ERROR, "not fired by event trigger manager");
+
+	foreach(lc, currentEventTriggerState->commandList)
+	{
+		char		relpersist = RELPERSISTENCE_PERMANENT;
+		CollectedCommand *cmd = lfirst(lc);
+		DeparsedCommandType cmdtype;
+
+		/* Rewrite DDL has been handled in table_rewrite trigger */
+		if (cmd->d.alterTable.rewrite)
+		{
+			RenameStmt *renameStmt = (RenameStmt *) cmd->parsetree;
+
+			if (renameStmt && renameStmt->relationType != OBJECT_TYPE &&
+				renameStmt->relationType != OBJECT_TABLE)
+				continue;
+		}
+
+		if (cmd->type == SCT_Simple &&
+			!OidIsValid(cmd->d.simple.address.objectId))
+			continue;
+
+		if (cmd->type == SCT_AlterTable)
+		{
+			relid = cmd->d.alterTable.objectId;
+			cmdtype = DCT_TableAlter;
+		}
+		else
+		{
+			/* Only SCT_Simple for now */
+			relid = cmd->d.simple.address.objectId;
+			cmdtype = DCT_SimpleCmd;
+		}
+
+		relkind = get_rel_relkind(relid);
+		if (relkind)
+			relpersist = get_rel_persistence(relid);
+
+		/*
+		 * Do not generate wal log for commands whose target table is a
+		 * temporary or unlogged table.
+		 *
+		 * XXX We may generate wal logs for unlogged tables in the future so
+		 * that unlogged tables can also be created and altered on the
+		 * subscriber side. This makes it possible to directly replay the SET
+		 * LOGGED command and the incoming rewrite message without creating a
+		 * new table.
+		 */
+		if (relpersist == RELPERSISTENCE_PERMANENT)
+		{
+			/*
+			 * Deparse the DDL command and WAL log it to allow decoding of the
+			 * same.
+			 */
+			ddl_deparse_context context;
+			char	   *json_string;
+
+			context.verbose_mode = false;
+
+			/*
+			 * Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
+			 * the minimum volatility level.
+			 */
+			context.max_volatility = PROVOLATILE_IMMUTABLE;
+
+			json_string = deparse_utility_command(cmd, &context);
+
+			if (json_string != NULL)
+			{
+				check_command_publishable(context, false);
+				LogLogicalDDLMessage("deparse", relid, cmdtype, json_string,
+									 strlen(json_string) + 1);
+			}
+		}
+	}
+
+	/* Drop commands are not part commandlist but handled here as part of SQLDropList */
+	slist_foreach(iter, &(currentEventTriggerState->SQLDropList))
+	{
+		SQLDropObject *obj;
+		EventTriggerData *trigdata;
+
+		trigdata = (EventTriggerData *) fcinfo->context;
+
+		obj = slist_container(SQLDropObject, next, iter.cur);
+
+		if (!obj->original)
+			continue;
+
+		if (strcmp(obj->objecttype, "table") == 0)
+		{
+			DeparsedCommandType cmdtype = DCT_TableDropEnd;
+			char	*command;
+
+			command = deparse_drop_command(obj->objidentity, obj->objecttype,
+									   trigdata->parsetree);
+			if (command)
+				LogLogicalDDLMessage("deparse", obj->address.objectId, cmdtype,
+									 command, strlen(command) + 1);
+		}
+	}
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * publication_deparse_table_init_write
+ *
+ * Deparse the ddl table create command and log it.
+ */
+Datum
+publication_deparse_table_init_write(PG_FUNCTION_ARGS)
+{
+	char		relpersist;
+	CollectedCommand *cmd;
+	ddl_deparse_context context;
+
+	if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+		elog(ERROR, "not fired by event trigger manager");
+
+	cmd = currentEventTriggerState->currentCommand;
+	Assert(cmd);
+
+	relpersist = get_rel_persistence(cmd->d.simple.address.objectId);
+
+	/*
+	 * Do not generate wal log for commands whose target table is a temporary
+	 * table.
+	 *
+	 * We will generate wal logs for unlogged tables so that unlogged tables
+	 * can also be created and altered on the subscriber side. This makes it
+	 * possible to directly replay the SET LOGGED command and the incoming
+	 * rewrite message without creating a new table.
+	 */
+	if (relpersist == RELPERSISTENCE_PERMANENT)
+	{
+		char	   *json_string;
+
+		context.verbose_mode = false;
+
+		/*
+		 * Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
+		 * the minimum volatility level.
+		 */
+		context.max_volatility = PROVOLATILE_IMMUTABLE;
+
+		/* Deparse the DDL command and WAL log it to allow decoding of the same. */
+		json_string = deparse_utility_command(cmd, &context);
+
+		if (json_string != NULL)
+		{
+			check_command_publishable(context, false);
+			LogLogicalDDLMessage("deparse", cmd->d.simple.address.objectId, DCT_SimpleCmd,
+								 json_string, strlen(json_string) + 1);
+		}
+	}
+
+	return PointerGetDatum(NULL);
+}
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index d91055a440..b22bbcd15a 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -36,6 +36,7 @@
 #include "access/xlogutils.h"
 #include "catalog/pg_control.h"
 #include "replication/decode.h"
+#include "replication/ddlmessage.h"
 #include "replication/logical.h"
 #include "replication/message.h"
 #include "replication/origin.h"
@@ -641,6 +642,46 @@ logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 							  message->message + message->prefix_size);
 }
 
+/*
+ * Handle rmgr LOGICALDDLMSG_ID records for DecodeRecordIntoReorderBuffer().
+ */
+void
+logicalddl_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+	SnapBuild  *builder = ctx->snapshot_builder;
+	XLogReaderState *r = buf->record;
+	TransactionId xid = XLogRecGetXid(r);
+	uint8		info = XLogRecGetInfo(r) & ~XLR_INFO_MASK;
+	RepOriginId origin_id = XLogRecGetOrigin(r);
+	xl_logical_ddl_message *message;
+
+	if (info != XLOG_LOGICAL_DDL_MESSAGE)
+		elog(ERROR, "unexpected RM_LOGICALDDLMSG_ID record type: %u", info);
+
+	ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(r), buf->origptr);
+
+	/*
+	 * If we don't have snapshot or we are just fast-forwarding, there is no
+	 * point in decoding ddl messages.
+	 */
+	if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT ||
+		ctx->fast_forward)
+		return;
+
+	message = (xl_logical_ddl_message *) XLogRecGetData(r);
+
+	if (message->dbId != ctx->slot->data.database ||
+		FilterByOrigin(ctx, origin_id))
+		return;
+
+	if (SnapBuildProcessChange(builder, xid, buf->origptr))
+		ReorderBufferQueueDDLMessage(ctx->reorder, xid, buf->endptr,
+									 message->message, /* first part of message is prefix */
+									 message->message_size,
+									 message->message + message->prefix_size,
+									 message->relid, message->cmdtype);
+}
+
 /*
  * Consolidated commit record handling between the different form of commit
  * records.
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 41243d0187..1d99fac116 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -33,6 +33,7 @@
 #include "fmgr.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/ddlmessage.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/origin.h"
@@ -73,6 +74,10 @@ static void truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 static void message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 							   XLogRecPtr message_lsn, bool transactional,
 							   const char *prefix, Size message_size, const char *message);
+static void ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+								  XLogRecPtr message_lsn, const char *prefix,
+								  Oid relid, DeparsedCommandType cmdtype,
+								  Size message_size, const char *message);
 
 /* streaming callbacks */
 static void stream_start_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
@@ -90,6 +95,11 @@ static void stream_change_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn
 static void stream_message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 									  XLogRecPtr message_lsn, bool transactional,
 									  const char *prefix, Size message_size, const char *message);
+static void stream_ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+										 XLogRecPtr message_lsn,
+										 const char *prefix,
+										 Oid relid, DeparsedCommandType cmdtype,
+										 Size message_size, const char *message);
 static void stream_truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 									   int nrelations, Relation relations[], ReorderBufferChange *change);
 
@@ -221,10 +231,11 @@ StartupDecodingContext(List *output_plugin_options,
 	ctx->reorder->apply_truncate = truncate_cb_wrapper;
 	ctx->reorder->commit = commit_cb_wrapper;
 	ctx->reorder->message = message_cb_wrapper;
+	ctx->reorder->ddl = ddl_cb_wrapper;
 
 	/*
 	 * To support streaming, we require start/stop/abort/commit/change
-	 * callbacks. The message and truncate callbacks are optional, similar to
+	 * callbacks. The message, ddl and truncate callbacks are optional, similar to
 	 * regular output plugins. We however enable streaming when at least one
 	 * of the methods is enabled so that we can easily identify missing
 	 * methods.
@@ -237,12 +248,13 @@ StartupDecodingContext(List *output_plugin_options,
 		(ctx->callbacks.stream_commit_cb != NULL) ||
 		(ctx->callbacks.stream_change_cb != NULL) ||
 		(ctx->callbacks.stream_message_cb != NULL) ||
+		(ctx->callbacks.stream_ddl_cb != NULL) ||
 		(ctx->callbacks.stream_truncate_cb != NULL);
 
 	/*
 	 * streaming callbacks
 	 *
-	 * stream_message and stream_truncate callbacks are optional, so we do not
+	 * stream_message, stream_ddl and stream_truncate callbacks are optional, so we do not
 	 * fail with ERROR when missing, but the wrappers simply do nothing. We
 	 * must set the ReorderBuffer callbacks to something, otherwise the calls
 	 * from there will crash (we don't want to move the checks there).
@@ -254,6 +266,7 @@ StartupDecodingContext(List *output_plugin_options,
 	ctx->reorder->stream_commit = stream_commit_cb_wrapper;
 	ctx->reorder->stream_change = stream_change_cb_wrapper;
 	ctx->reorder->stream_message = stream_message_cb_wrapper;
+	ctx->reorder->stream_ddl = stream_ddl_cb_wrapper;
 	ctx->reorder->stream_truncate = stream_truncate_cb_wrapper;
 
 
@@ -1260,6 +1273,44 @@ message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 	error_context_stack = errcallback.previous;
 }
 
+static void
+ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+					  XLogRecPtr message_lsn,
+					  const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+					  Size message_size,
+					  const char *message)
+{
+	LogicalDecodingContext *ctx = cache->private_data;
+	LogicalErrorCallbackState state;
+	ErrorContextCallback errcallback;
+
+	Assert(!ctx->fast_forward);
+
+	if (ctx->callbacks.ddl_cb == NULL)
+		return;
+
+	/* Push callback + info on the error context stack */
+	state.ctx = ctx;
+	state.callback_name = "ddl";
+	state.report_location = message_lsn;
+	errcallback.callback = output_plugin_error_callback;
+	errcallback.arg = (void *) &state;
+	errcallback.previous = error_context_stack;
+	error_context_stack = &errcallback;
+
+	/* set output state */
+	ctx->accept_writes = true;
+	ctx->write_xid = txn->xid;
+	ctx->write_location = message_lsn;
+
+	/* do the actual work: call callback */
+	ctx->callbacks.ddl_cb(ctx, txn, message_lsn, prefix, relid, cmdtype,
+								 message_size, message);
+
+	/* Pop the error context stack */
+	error_context_stack = errcallback.previous;
+}
+
 static void
 stream_start_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 						XLogRecPtr first_lsn)
@@ -1575,6 +1626,48 @@ stream_message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 	error_context_stack = errcallback.previous;
 }
 
+static void
+stream_ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+							 XLogRecPtr message_lsn,
+							 const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+							 Size message_size,
+							 const char *message)
+{
+	LogicalDecodingContext *ctx = cache->private_data;
+	LogicalErrorCallbackState state;
+	ErrorContextCallback errcallback;
+
+	Assert(!ctx->fast_forward);
+
+	/* We're only supposed to call this when streaming is supported. */
+	Assert(ctx->streaming);
+
+	/* this callback is optional */
+	if (ctx->callbacks.stream_ddl_cb == NULL)
+		return;
+
+	/* Push callback + info on the error context stack */
+	state.ctx = ctx;
+	state.callback_name = "stream_ddl";
+	state.report_location = message_lsn;
+	errcallback.callback = output_plugin_error_callback;
+	errcallback.arg = (void *) &state;
+	errcallback.previous = error_context_stack;
+	error_context_stack = &errcallback;
+
+	/* set output state */
+	ctx->accept_writes = true;
+	ctx->write_xid = txn->xid;
+	ctx->write_location = message_lsn;
+
+	/* do the actual work: call callback */
+	ctx->callbacks.stream_ddl_cb(ctx, txn, message_lsn, prefix, relid,
+										cmdtype, message_size, message);
+
+	/* Pop the error context stack */
+	error_context_stack = errcallback.previous;
+}
+
 static void
 stream_truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 						   int nrelations, Relation relations[],
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 55a24c02c9..bececc3f93 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -29,6 +29,7 @@
 #include "nodes/makefuncs.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
+#include "replication/ddlmessage.h"
 #include "replication/message.h"
 #include "storage/fd.h"
 #include "utils/array.h"
@@ -375,3 +376,26 @@ pg_logical_emit_message_text(PG_FUNCTION_ARGS)
 	/* bytea and text are compatible */
 	return pg_logical_emit_message_bytea(fcinfo);
 }
+
+/*
+ * SQL function for writing logical decoding DDL message into WAL.
+ */
+Datum
+pg_logical_emit_ddl_message_bytea(PG_FUNCTION_ARGS)
+{
+	char	   *prefix = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	Oid			relid = PG_GETARG_OID(1);
+	DeparsedCommandType cmdtype = PG_GETARG_INT16(2);
+	char	   *data = text_to_cstring(PG_GETARG_TEXT_PP(3));
+	XLogRecPtr	lsn;
+
+	lsn = LogLogicalDDLMessage(prefix, relid, cmdtype, data, strlen(data));
+	PG_RETURN_LSN(lsn);
+}
+
+Datum
+pg_logical_emit_ddl_message_text(PG_FUNCTION_ARGS)
+{
+	/* bytea and text are compatible */
+	return pg_logical_emit_ddl_message_bytea(fcinfo);
+}
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..05e775697e 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -2,6 +2,8 @@
 
 backend_sources += files(
   'applyparallelworker.c',
+  'ddlmessage.c',
+  'ddltrigger.c',
   'decode.c',
   'launcher.c',
   'logical.c',
diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
index f308713275..7ec3b71662 100644
--- a/src/backend/replication/logical/proto.c
+++ b/src/backend/replication/logical/proto.c
@@ -663,6 +663,47 @@ logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn,
 	pq_sendbytes(out, message, sz);
 }
 
+/*
+ * Read DDL from stream
+ */
+char *
+logicalrep_read_ddl(StringInfo in, XLogRecPtr *lsn,
+						   const char **prefix,
+						   Size *sz)
+{
+	uint8 flags;
+	char *msg;
+
+	flags = pq_getmsgint(in, 1);
+	if (flags != 0)
+		elog(ERROR, "unrecognized flags %u in ddl message", flags);
+
+	*lsn = pq_getmsgint64(in);
+	*prefix = pq_getmsgstring(in);
+	*sz = pq_getmsgint(in, 4);
+	msg = (char *) pq_getmsgbytes(in, *sz);
+
+	return msg;
+}
+
+/*
+ * Write DDL to stream
+ */
+void
+logicalrep_write_ddl(StringInfo out, XLogRecPtr lsn,
+							const char *prefix, Size sz, const char *message)
+{
+	uint8		flags = 0;
+
+	pq_sendbyte(out, LOGICAL_REP_MSG_DDL);
+
+	pq_sendint8(out, flags);
+	pq_sendint64(out, lsn);
+	pq_sendstring(out, prefix);
+	pq_sendint32(out, sz);
+	pq_sendbytes(out, message, sz);
+}
+
 /*
  * Write relation description to the output stream.
  */
@@ -1238,6 +1279,8 @@ logicalrep_message_type(LogicalRepMsgType action)
 			return "TYPE";
 		case LOGICAL_REP_MSG_MESSAGE:
 			return "MESSAGE";
+		case LOGICAL_REP_MSG_DDL:
+			return "DDL";
 		case LOGICAL_REP_MSG_BEGIN_PREPARE:
 			return "BEGIN PREPARE";
 		case LOGICAL_REP_MSG_PREPARE:
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 26d252bd87..d21042e725 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -94,6 +94,7 @@
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "replication/ddlmessage.h"
 #include "replication/logical.h"
 #include "replication/reorderbuffer.h"
 #include "replication/slot.h"
@@ -516,6 +517,14 @@ ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change,
 				pfree(change->data.msg.message);
 			change->data.msg.message = NULL;
 			break;
+		case REORDER_BUFFER_CHANGE_DDL:
+			if (change->data.ddl.prefix != NULL)
+				pfree(change->data.ddl.prefix);
+			change->data.ddl.prefix = NULL;
+			if (change->data.ddl.message != NULL)
+				pfree(change->data.ddl.message);
+			change->data.ddl.message = NULL;
+			break;
 		case REORDER_BUFFER_CHANGE_INVALIDATION:
 			if (change->data.inval.invalidations)
 				pfree(change->data.inval.invalidations);
@@ -895,6 +904,36 @@ ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
 	}
 }
 
+/*
+ * A transactional DDL message is queued to be processed upon commit.
+ */
+void
+ReorderBufferQueueDDLMessage(ReorderBuffer *rb, TransactionId xid,
+							 XLogRecPtr lsn, const char *prefix,
+							 Size message_size, const char *message,
+							 Oid relid, DeparsedCommandType cmdtype)
+{
+	MemoryContext oldcontext;
+	ReorderBufferChange *change;
+
+	Assert(TransactionIdIsValid(xid));
+
+	oldcontext = MemoryContextSwitchTo(rb->context);
+
+	change = ReorderBufferGetChange(rb);
+	change->action = REORDER_BUFFER_CHANGE_DDL;
+	change->data.ddl.prefix = pstrdup(prefix);
+	change->data.ddl.relid = relid;
+	change->data.ddl.cmdtype = cmdtype;
+	change->data.ddl.message_size = message_size;
+	change->data.ddl.message = palloc(message_size);
+	memcpy(change->data.ddl.message, message, message_size);
+
+	ReorderBufferQueueChange(rb, xid, lsn, change, false);
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
 /*
  * AssertTXNLsnOrder
  *		Verify LSN ordering of transaction lists in the reorderbuffer
@@ -1997,6 +2036,29 @@ ReorderBufferApplyMessage(ReorderBuffer *rb, ReorderBufferTXN *txn,
 					change->data.msg.message);
 }
 
+/*
+ * Helper function for ReorderBufferProcessTXN for applying the DDL message.
+ */
+static inline void
+ReorderBufferApplyDDLMessage(ReorderBuffer *rb, ReorderBufferTXN *txn,
+							 ReorderBufferChange *change, bool streaming)
+{
+	if (streaming)
+		rb->stream_ddl(rb, txn, change->lsn,
+							  change->data.ddl.prefix,
+							  change->data.ddl.relid,
+							  change->data.ddl.cmdtype,
+							  change->data.ddl.message_size,
+							  change->data.ddl.message);
+	else
+		rb->ddl(rb, txn, change->lsn,
+					   change->data.ddl.prefix,
+					   change->data.ddl.relid,
+					   change->data.ddl.cmdtype,
+					   change->data.ddl.message_size,
+					   change->data.ddl.message);
+}
+
 /*
  * Function to store the command id and snapshot at the end of the current
  * stream so that we can reuse the same while sending the next stream.
@@ -2379,6 +2441,10 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
 					ReorderBufferApplyMessage(rb, txn, change, streaming);
 					break;
 
+				case REORDER_BUFFER_CHANGE_DDL:
+					ReorderBufferApplyDDLMessage(rb, txn, change, streaming);
+					break;
+
 				case REORDER_BUFFER_CHANGE_INVALIDATION:
 					/* Execute the invalidation messages locally */
 					ReorderBufferExecuteInvalidations(change->data.inval.ninvalidations,
@@ -3835,6 +3901,39 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
 					   change->data.msg.message_size);
 				data += change->data.msg.message_size;
 
+				break;
+			}
+		case REORDER_BUFFER_CHANGE_DDL:
+			{
+				char	   *data;
+				Size		prefix_size = strlen(change->data.ddl.prefix) + 1;
+
+				sz += prefix_size + change->data.ddl.message_size +
+					sizeof(Size) + sizeof(Oid) + sizeof(DeparsedCommandType) + sizeof(Size);
+				ReorderBufferSerializeReserve(rb, sz);
+
+				data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
+
+				/* might have been reallocated above */
+				ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+				/* write the prefix, relid and cmdtype including the size */
+				memcpy(data, &prefix_size, sizeof(Size));
+				data += sizeof(Size);
+				memcpy(data, &change->data.ddl.relid, sizeof(Oid));
+				data += sizeof(Oid);
+				memcpy(data, &change->data.ddl.cmdtype, sizeof(DeparsedCommandType));
+				data += sizeof(DeparsedCommandType);
+				memcpy(data, change->data.ddl.prefix, prefix_size);
+				data += prefix_size;
+
+				/* write the message including the size */
+				memcpy(data, &change->data.ddl.message_size, sizeof(Size));
+				data += sizeof(Size);
+				memcpy(data, change->data.ddl.message,
+					   change->data.ddl.message_size);
+				data += change->data.ddl.message_size;
+
 				break;
 			}
 		case REORDER_BUFFER_CHANGE_INVALIDATION:
@@ -4149,6 +4248,15 @@ ReorderBufferChangeSize(ReorderBufferChange *change)
 				sz += prefix_size + change->data.msg.message_size +
 					sizeof(Size) + sizeof(Size);
 
+				break;
+			}
+		case REORDER_BUFFER_CHANGE_DDL:
+			{
+				Size		prefix_size = strlen(change->data.ddl.prefix) + 1;
+
+				sz += prefix_size + change->data.ddl.message_size +
+					sizeof(Size) + sizeof(Size) + sizeof(Oid) + sizeof(DeparsedCommandType);
+
 				break;
 			}
 		case REORDER_BUFFER_CHANGE_INVALIDATION:
@@ -4426,6 +4534,33 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
 					   change->data.msg.message_size);
 				data += change->data.msg.message_size;
 
+				break;
+			}
+		case REORDER_BUFFER_CHANGE_DDL:
+			{
+				Size		prefix_size;
+
+				/* read prefix */
+				memcpy(&prefix_size, data, sizeof(Size));
+				data += sizeof(Size);
+				memcpy(&change->data.ddl.relid, data, sizeof(Oid));
+				data += sizeof(Oid);
+				memcpy(&change->data.ddl.cmdtype, data, sizeof(DeparsedCommandType));
+				data += sizeof(DeparsedCommandType);
+				change->data.ddl.prefix = MemoryContextAlloc(rb->context, prefix_size);
+				memcpy(change->data.ddl.prefix, data, prefix_size);
+				Assert(change->data.ddl.prefix[prefix_size - 1] == '\0');
+				data += prefix_size;
+
+				/* read the message */
+				memcpy(&change->data.msg.message_size, data, sizeof(Size));
+				data += sizeof(Size);
+				change->data.msg.message = MemoryContextAlloc(rb->context,
+															  change->data.msg.message_size);
+				memcpy(change->data.msg.message, data,
+					   change->data.msg.message_size);
+				data += change->data.msg.message_size;
+
 				break;
 			}
 		case REORDER_BUFFER_CHANGE_INVALIDATION:
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 0ee764d68f..5f11771661 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -166,6 +166,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/analyze.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -191,7 +192,10 @@
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "tcop/ddldeparse.h"
+#include "tcop/pquery.h"
 #include "tcop/tcopprot.h"
+#include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
@@ -3277,6 +3281,230 @@ apply_handle_truncate(StringInfo s)
 	end_replication_step();
 }
 
+/*
+ * CREATE TABLE AS and SELECT INTO require special handling to force them
+ * to skip populating data from the source table on the subscriber. Data
+ * should be replicated from the publisher instead.
+ */
+static void
+preprocess_ddl_create_table(RawStmt *command)
+{
+	CommandTag	commandTag;
+
+	commandTag = CreateCommandTag((Node *) command);
+
+	switch (commandTag)
+	{
+		case CMDTAG_CREATE_TABLE_AS:
+		case CMDTAG_SELECT_INTO:
+			{
+				CreateTableAsStmt *castmt = (CreateTableAsStmt *) command->stmt;
+
+				if (castmt->objtype == OBJECT_TABLE)
+				{
+					castmt->into->skipData = true;
+				}
+			}
+			break;
+		case CMDTAG_SELECT:
+			{
+				SelectStmt *sstmt = (SelectStmt *) command->stmt;
+
+				if (sstmt->intoClause != NULL)
+					sstmt->intoClause->skipData = true;
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+/*
+ * Handle CREATE TABLE command
+ *
+ * Call AddSubscriptionRelState for CREATE TABLE command to set the relstate to
+ * SUBREL_STATE_READY so DML changes on this new table can be replicated without
+ * having to manually run "ALTER SUBSCRIPTION ... REFRESH PUBLICATION"
+ */
+static void
+postprocess_ddl_create_table(RawStmt *command)
+{
+	CommandTag	commandTag;
+	RangeVar   *rv = NULL;
+	Oid			relid;
+	Oid			relnamespace_oid = InvalidOid;
+	CreateStmt *cstmt;
+	char	   *schemaname = NULL;
+	char	   *relname = NULL;
+
+	commandTag = CreateCommandTag((Node *) command);
+
+	if (commandTag != CMDTAG_CREATE_TABLE)
+		return;
+
+	cstmt = (CreateStmt *) command->stmt;
+	rv = cstmt->relation;
+	if (!rv)
+		return;
+
+	schemaname = rv->schemaname;
+	relname = rv->relname;
+
+	if (schemaname != NULL)
+		relnamespace_oid = get_namespace_oid(schemaname, false);
+
+	if (OidIsValid(relnamespace_oid))
+		relid = get_relname_relid(relname, relnamespace_oid);
+	else
+		relid = RelnameGetRelid(relname);
+
+	if (OidIsValid(relid))
+	{
+		AddSubscriptionRelState(MySubscription->oid, relid,
+								SUBREL_STATE_READY,
+								InvalidXLogRecPtr);
+		ereport(DEBUG1,
+				(errmsg_internal("table \"%s\" added to subscription \"%s\"",
+								 relname, MySubscription->name)));
+	}
+}
+
+/*
+ * Handle DDL commands
+ *
+ * Handle DDL replication messages. Convert the json string into a query
+ * string and run it through the query portal.
+ */
+static void
+apply_handle_ddl(StringInfo s)
+{
+	XLogRecPtr	lsn;
+	const char *prefix = NULL;
+	char	   *message = NULL;
+	char	   *ddl_command;
+	Size		sz;
+	List	   *parsetree_list;
+	ListCell   *parsetree_item;
+	DestReceiver *receiver;
+	MemoryContext oldcontext;
+	const char *save_debug_query_string = debug_query_string;
+
+	message = logicalrep_read_ddl(s, &lsn, &prefix, &sz);
+
+	SetCurrentStatementStartTimestamp();
+
+	if (!IsTransactionState())
+	{
+		StartTransactionCommand();
+		maybe_reread_subscription();
+	}
+
+	MemoryContextSwitchTo(ApplyMessageContext);
+
+	ddl_command = deparse_ddl_json_to_string(message);
+	debug_query_string = ddl_command;
+
+	/* DestNone for logical replication */
+	receiver = CreateDestReceiver(DestNone);
+	parsetree_list = pg_parse_query(ddl_command);
+
+	foreach(parsetree_item, parsetree_list)
+	{
+		List	   *plantree_list;
+		List	   *querytree_list;
+		RawStmt    *command = (RawStmt *) lfirst(parsetree_item);
+		CommandTag	commandTag;
+		MemoryContext per_parsetree_context = NULL;
+		Portal		portal;
+		bool		snapshot_set = false;
+
+		commandTag = CreateCommandTag((Node *) command);
+
+		/* If we got a cancel signal in parsing or prior command, quit */
+		CHECK_FOR_INTERRUPTS();
+
+		/* Remove data population from the command */
+		preprocess_ddl_create_table(command);
+
+		/*
+		 * Set up a snapshot if parse analysis/planning will need one.
+		 */
+		if (analyze_requires_snapshot(command))
+		{
+			PushActiveSnapshot(GetTransactionSnapshot());
+			snapshot_set = true;
+		}
+
+		/*
+		 * We do the work for each parsetree in a short-lived context, to
+		 * limit the memory used when there are many commands in the string.
+		 */
+		per_parsetree_context =
+			AllocSetContextCreate(CurrentMemoryContext,
+								  "execute_sql_string per-statement context",
+								  ALLOCSET_DEFAULT_SIZES);
+		oldcontext = MemoryContextSwitchTo(per_parsetree_context);
+
+		querytree_list = pg_analyze_and_rewrite_fixedparams(command,
+															ddl_command,
+															NULL, 0, NULL);
+
+		plantree_list = pg_plan_queries(querytree_list, ddl_command, 0, NULL);
+
+		/* Done with the snapshot used for parsing/planning */
+		if (snapshot_set)
+			PopActiveSnapshot();
+
+		portal = CreatePortal("logical replication", true, true);
+
+		/*
+		 * We don't have to copy anything into the portal, because everything
+		 * we are passing here is in ApplyMessageContext or the
+		 * per_parsetree_context, and so will outlive the portal anyway.
+		 */
+		PortalDefineQuery(portal,
+						  NULL,
+						  ddl_command,
+						  commandTag,
+						  plantree_list,
+						  NULL);
+
+		/*
+		 * Start the portal.  No parameters here.
+		 */
+		PortalStart(portal, NULL, 0, InvalidSnapshot);
+
+		/*
+		 * Switch back to transaction context for execution.
+		 */
+		MemoryContextSwitchTo(oldcontext);
+
+		(void) PortalRun(portal,
+						 FETCH_ALL,
+						 true,
+						 true,
+						 receiver,
+						 receiver,
+						 NULL);
+
+		PortalDrop(portal, false);
+
+		CommandCounterIncrement();
+
+		/*
+		 * Table created by DDL replication (database level) is automatically
+		 * added to the subscription here.
+		 */
+		postprocess_ddl_create_table(command);
+
+		/* Now we may drop the per-parsetree context, if one was created. */
+		MemoryContextDelete(per_parsetree_context);
+	}
+
+	debug_query_string = save_debug_query_string;
+
+	CommandCounterIncrement();
+}
 
 /*
  * Logical replication protocol message dispatcher.
@@ -3342,6 +3570,10 @@ apply_dispatch(StringInfo s)
 			 */
 			break;
 
+		case LOGICAL_REP_MSG_DDL:
+			apply_handle_ddl(s);
+			break;
+
 		case LOGICAL_REP_MSG_STREAM_START:
 			apply_handle_stream_start(s);
 			break;
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index b08ca55041..af634b25bc 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -18,6 +18,7 @@
 #include "catalog/pg_publication_rel.h"
 #include "catalog/pg_subscription.h"
 #include "commands/defrem.h"
+#include "commands/publicationcmds.h"
 #include "commands/subscriptioncmds.h"
 #include "executor/executor.h"
 #include "fmgr.h"
@@ -55,6 +56,11 @@ static void pgoutput_message(LogicalDecodingContext *ctx,
 							 ReorderBufferTXN *txn, XLogRecPtr message_lsn,
 							 bool transactional, const char *prefix,
 							 Size sz, const char *message);
+static void pgoutput_ddl(LogicalDecodingContext *ctx,
+						 ReorderBufferTXN *txn, XLogRecPtr message_lsn,
+						 const char *prefix, Oid relid,
+						 DeparsedCommandType cmdtype,
+						 Size sz, const char *message);
 static bool pgoutput_origin_filter(LogicalDecodingContext *ctx,
 								   RepOriginId origin_id);
 static void pgoutput_begin_prepare_txn(LogicalDecodingContext *ctx,
@@ -207,6 +213,13 @@ typedef struct RelationSyncEntry
 typedef struct PGOutputTxnData
 {
 	bool		sent_begin_txn; /* flag indicating whether BEGIN has been sent */
+	/*
+	 * Maintain list of deleted oids which are added at command start.
+	 * This is required because at command end when the actual command is sent
+	 * the catalogs have been modified and it is not possible to know if the oid
+	 * was referring to an object that was part of the publication.
+	 */
+	List	   *deleted_relids; /* maintain list of deleted table oids */
 } PGOutputTxnData;
 
 /* Map used to remember which relation schemas we sent. */
@@ -254,6 +267,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
 	cb->change_cb = pgoutput_change;
 	cb->truncate_cb = pgoutput_truncate;
 	cb->message_cb = pgoutput_message;
+	cb->ddl_cb = pgoutput_ddl;
 	cb->commit_cb = pgoutput_commit_txn;
 
 	cb->begin_prepare_cb = pgoutput_begin_prepare_txn;
@@ -270,6 +284,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
 	cb->stream_commit_cb = pgoutput_stream_commit;
 	cb->stream_change_cb = pgoutput_change;
 	cb->stream_message_cb = pgoutput_message;
+	cb->stream_ddl_cb = pgoutput_ddl;
 	cb->stream_truncate_cb = pgoutput_truncate;
 	/* transaction streaming - two-phase commit */
 	cb->stream_prepare_cb = pgoutput_stream_prepare_txn;
@@ -505,6 +520,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
 
 		/* Init publication state. */
 		data->publications = NIL;
+		data->deleted_relids = NIL;
 		publications_valid = false;
 
 		/*
@@ -533,6 +549,34 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
 	}
 }
 
+/* Initialize the per-transaction private data for the given transaction. */
+static void
+init_txn_data(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
+{
+	PGOutputTxnData *txndata;
+
+	if (txn->output_plugin_private != NULL)
+		return;
+
+	txndata = MemoryContextAllocZero(ctx->context, sizeof(PGOutputTxnData));
+
+	txn->output_plugin_private = txndata;
+}
+
+/* Clean up the per-transaction private data for the given transaction. */
+static void
+clean_txn_data(ReorderBufferTXN *txn)
+{
+	PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
+
+	if (txndata == NULL)
+		return;
+
+	list_free(txndata->deleted_relids);
+	pfree(txndata);
+	txn->output_plugin_private = NULL;
+}
+
 /*
  * BEGIN callback.
  *
@@ -546,10 +590,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
 static void
 pgoutput_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
 {
-	PGOutputTxnData *txndata = MemoryContextAllocZero(ctx->context,
-													  sizeof(PGOutputTxnData));
-
-	txn->output_plugin_private = txndata;
+	init_txn_data(ctx, txn);
 }
 
 /*
@@ -594,8 +635,7 @@ pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
 	 */
 	sent_begin_txn = txndata->sent_begin_txn;
 	OutputPluginUpdateProgress(ctx, !sent_begin_txn);
-	pfree(txndata);
-	txn->output_plugin_private = NULL;
+	clean_txn_data(txn);
 
 	if (!sent_begin_txn)
 	{
@@ -637,6 +677,8 @@ pgoutput_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
 	OutputPluginPrepareWrite(ctx, true);
 	logicalrep_write_prepare(ctx->out, txn, prepare_lsn);
 	OutputPluginWrite(ctx, true);
+
+	clean_txn_data(txn);
 }
 
 /*
@@ -1665,6 +1707,107 @@ pgoutput_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
 	OutputPluginWrite(ctx, true);
 }
 
+/* Check if the given object is published. */
+static bool
+is_object_published(LogicalDecodingContext *ctx, Oid objid)
+{
+	Relation	relation = NULL;
+	RelationSyncEntry *relentry;
+	PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
+
+	/* First check the DDL command filter. */
+	switch (get_rel_relkind(objid))
+	{
+		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
+			relation = RelationIdGetRelation(objid);
+			relentry = get_rel_sync_entry(data, relation);
+			RelationClose(relation);
+
+			/* Only send this ddl if we publish ddl message. */
+			if (relentry->pubactions.pubddl_table)
+				return true;
+
+			break;
+		default:
+			/* unsupported objects */
+			return false;
+	}
+
+	return false;
+}
+
+/*
+ * Send the decoded DDL message.
+ */
+static void
+pgoutput_ddl(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+			 XLogRecPtr message_lsn,
+			 const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+			 Size sz, const char *message)
+{
+	PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
+
+	/*
+	 * Check if the given object is published. Note that for dropped objects,
+	 * we cannot get the required information from the catalog, so we skip the
+	 * check for them.
+	 */
+	if (cmdtype != DCT_TableDropEnd && !is_object_published(ctx, relid))
+		return;
+
+	switch (cmdtype)
+	{
+		case DCT_TableDropStart:
+			{
+				MemoryContext	oldctx;
+
+				init_txn_data(ctx, txn);
+
+				txndata = (PGOutputTxnData *) txn->output_plugin_private;
+
+				/*
+				 * On DROP start, add the relid to a deleted_relid list if the
+				 * relid is part of a publication that supports ddl
+				 * publication. We need this because on DROP end, the relid
+				 * will no longer be valid. Later on Drop end, verify that the
+				 * drop is for a relid that is on the deleted_rid list, and
+				 * only then send the ddl message.
+				 */
+				oldctx = MemoryContextSwitchTo(ctx->context);
+				txndata->deleted_relids = lappend_oid(txndata->deleted_relids,
+													  relid);
+				MemoryContextSwitchTo(oldctx);
+			}
+			return;
+
+		case DCT_TableDropEnd:
+			if (!list_member_oid(txndata->deleted_relids, relid))
+				return;
+
+			txndata->deleted_relids = list_delete_oid(txndata->deleted_relids,
+													  relid);
+			break;
+
+		case DCT_TableAlter:
+		case DCT_SimpleCmd:
+			/* do nothing */
+			break;
+
+		default:
+			elog(ERROR, "unsupported type %d", cmdtype);
+			break;
+	}
+
+	/* Send BEGIN if we haven't yet */
+	if (txndata && !txndata->sent_begin_txn)
+		pgoutput_send_begin(ctx, txn);
+
+	OutputPluginPrepareWrite(ctx, true);
+	logicalrep_write_ddl(ctx->out, message_lsn, prefix, sz, message);
+	OutputPluginWrite(ctx, true);
+}
+
 /*
  * Return true if the data is associated with an origin and the user has
  * requested the changes that don't have an origin, false otherwise.
@@ -1813,6 +1956,7 @@ pgoutput_stream_abort(struct LogicalDecodingContext *ctx,
 	OutputPluginWrite(ctx, true);
 
 	cleanup_rel_sync_cache(toptxn->xid, false);
+	clean_txn_data(txn);
 }
 
 /*
@@ -1838,6 +1982,7 @@ pgoutput_stream_commit(struct LogicalDecodingContext *ctx,
 	OutputPluginWrite(ctx, true);
 
 	cleanup_rel_sync_cache(txn->xid, true);
+	clean_txn_data(txn);
 }
 
 /*
@@ -1856,6 +2001,8 @@ pgoutput_stream_prepare_txn(LogicalDecodingContext *ctx,
 	OutputPluginPrepareWrite(ctx, true);
 	logicalrep_write_stream_prepare(ctx->out, txn, prepare_lsn);
 	OutputPluginWrite(ctx, true);
+
+	clean_txn_data(txn);
 }
 
 /*
@@ -1972,8 +2119,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
 		entry->replicate_valid = false;
 		entry->schema_sent = false;
 		entry->streamed_txns = NIL;
-		entry->pubactions.pubinsert = entry->pubactions.pubupdate =
-			entry->pubactions.pubdelete = entry->pubactions.pubtruncate = false;
+		memset(&entry->pubactions, 0, sizeof(entry->pubactions));
 		entry->new_slot = NULL;
 		entry->old_slot = NULL;
 		memset(entry->exprstate, 0, sizeof(entry->exprstate));
@@ -2027,10 +2173,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
 		entry->streamed_txns = NIL;
 		bms_free(entry->columns);
 		entry->columns = NULL;
-		entry->pubactions.pubinsert = false;
-		entry->pubactions.pubupdate = false;
-		entry->pubactions.pubdelete = false;
-		entry->pubactions.pubtruncate = false;
+		memset(&entry->pubactions, 0, sizeof(entry->pubactions));
 
 		/*
 		 * Tuple slots cleanups. (Will be rebuilt later if needed).
@@ -2129,53 +2272,61 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
 					publish = true;
 			}
 
-			/*
-			 * If the relation is to be published, determine actions to
-			 * publish, and list of columns, if appropriate.
-			 *
-			 * Don't publish changes for partitioned tables, because
-			 * publishing those of its partitions suffices, unless partition
-			 * changes won't be published due to pubviaroot being set.
-			 */
-			if (publish &&
-				(relkind != RELKIND_PARTITIONED_TABLE || pub->pubviaroot))
+			if (publish)
 			{
-				entry->pubactions.pubinsert |= pub->pubactions.pubinsert;
-				entry->pubactions.pubupdate |= pub->pubactions.pubupdate;
-				entry->pubactions.pubdelete |= pub->pubactions.pubdelete;
-				entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate;
-
 				/*
-				 * We want to publish the changes as the top-most ancestor
-				 * across all publications. So we need to check if the already
-				 * calculated level is higher than the new one. If yes, we can
-				 * ignore the new value (as it's a child). Otherwise the new
-				 * value is an ancestor, so we keep it.
+				 * The behavior of DDL logical replication is unrelated to
+				 * pubviaroot.
 				 */
-				if (publish_ancestor_level > ancestor_level)
-					continue;
+				entry->pubactions.pubddl_table |= pub->pubactions.pubddl_table;
 
 				/*
-				 * If we found an ancestor higher up in the tree, discard the
-				 * list of publications through which we replicate it, and use
-				 * the new ancestor.
+				 * If the relation is to be published, determine actions to
+				 * publish, and list of columns, if appropriate.
+				 *
+				 * Don't publish changes for partitioned tables, because
+				 * publishing those of its partitions suffices, unless partition
+				 * changes won't be published due to pubviaroot being set.
 				 */
-				if (publish_ancestor_level < ancestor_level)
+				if (relkind != RELKIND_PARTITIONED_TABLE || pub->pubviaroot)
 				{
-					publish_as_relid = pub_relid;
-					publish_ancestor_level = ancestor_level;
+					entry->pubactions.pubinsert |= pub->pubactions.pubinsert;
+					entry->pubactions.pubupdate |= pub->pubactions.pubupdate;
+					entry->pubactions.pubdelete |= pub->pubactions.pubdelete;
+					entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate;
 
-					/* reset the publication list for this relation */
-					rel_publications = NIL;
-				}
-				else
-				{
-					/* Same ancestor level, has to be the same OID. */
-					Assert(publish_as_relid == pub_relid);
-				}
+					/*
+					 * We want to publish the changes as the top-most ancestor
+					 * across all publications. So we need to check if the already
+					 * calculated level is higher than the new one. If yes, we can
+					 * ignore the new value (as it's a child). Otherwise the new
+					 * value is an ancestor, so we keep it.
+					 */
+					if (publish_ancestor_level > ancestor_level)
+						continue;
+
+					/*
+					 * If we found an ancestor higher up in the tree, discard the
+					 * list of publications through which we replicate it, and use
+					 * the new ancestor.
+					 */
+					if (publish_ancestor_level < ancestor_level)
+					{
+						publish_as_relid = pub_relid;
+						publish_ancestor_level = ancestor_level;
+
+						/* reset the publication list for this relation */
+						rel_publications = NIL;
+					}
+					else
+					{
+						/* Same ancestor level, has to be the same OID. */
+						Assert(publish_as_relid == pub_relid);
+					}
 
-				/* Track publications for this ancestor. */
-				rel_publications = lappend(rel_publications, pub);
+					/* Track publications for this ancestor. */
+					rel_publications = lappend(rel_publications, pub);
+				}
 			}
 		}
 
diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c
index 4bd713a0b4..a31b061eb7 100644
--- a/src/backend/tcop/cmdtag.c
+++ b/src/backend/tcop/cmdtag.c
@@ -26,10 +26,11 @@ typedef struct CommandTagBehavior
 	const bool	table_rewrite_ok;
 	const bool	display_rowcount;	/* should the number of rows affected be
 									 * shown in the command completion string */
+	const bool	ddl_replication_ok;
 } CommandTagBehavior;
 
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
-	{ name, (uint8) (sizeof(name) - 1), evtrgok, rwrok, rowcnt },
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, ddl_repl_ok) \
+	{ name, (uint8) (sizeof(name) - 1), evtrgok, rwrok, rowcnt, ddl_repl_ok },
 
 static const CommandTagBehavior tag_behavior[COMMAND_TAG_NEXTTAG] = {
 #include "tcop/cmdtaglist.h"
@@ -57,6 +58,21 @@ GetCommandTagNameAndLen(CommandTag commandTag, Size *len)
 	return tag_behavior[commandTag].name;
 }
 
+CommandTag *
+GetCommandTagsForDDLRepl(int *ncommands)
+{
+	CommandTag *ddlrepl_commands = palloc0(COMMAND_TAG_NEXTTAG * sizeof(CommandTag));
+	*ncommands = 0;
+
+	for(int i = 0; i < COMMAND_TAG_NEXTTAG; i++)
+	{
+		if (tag_behavior[i].ddl_replication_ok)
+			ddlrepl_commands[(*ncommands)++] = (CommandTag) i;
+	}
+
+	return ddlrepl_commands;
+}
+
 bool
 command_tag_display_rowcount(CommandTag commandTag)
 {
@@ -75,6 +91,12 @@ command_tag_table_rewrite_ok(CommandTag commandTag)
 	return tag_behavior[commandTag].table_rewrite_ok;
 }
 
+bool
+command_tag_ddl_replication_ok(CommandTag commandTag)
+{
+	return tag_behavior[commandTag].ddl_replication_ok;
+}
+
 /*
  * Search CommandTag by name
  *
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3aa6270a88..6c834ee39f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -895,7 +895,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
 		case T_CreateEventTrigStmt:
 			/* no event triggers on event triggers */
-			CreateEventTrigger((CreateEventTrigStmt *) parsetree);
+			CreateEventTrigger((CreateEventTrigStmt *) parsetree, false);
 			break;
 
 		case T_AlterEventTrigStmt:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8a08463c2b..f3022f08c1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5720,6 +5720,7 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
 		pubdesc->pubactions.pubupdate |= pubform->pubupdate;
 		pubdesc->pubactions.pubdelete |= pubform->pubdelete;
 		pubdesc->pubactions.pubtruncate |= pubform->pubtruncate;
+		pubdesc->pubactions.pubddl_table |= pubform->pubddl_table;
 
 		/*
 		 * Check if all columns referenced in the filter expression are part
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..334726cd0d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -51,6 +51,7 @@
 #include "catalog/pg_largeobject_d.h"
 #include "catalog/pg_largeobject_metadata_d.h"
 #include "catalog/pg_proc_d.h"
+#include "catalog/pg_publication.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_trigger_d.h"
 #include "catalog/pg_type_d.h"
@@ -4047,6 +4048,7 @@ getPublications(Archive *fout, int *numPublications)
 	int			i_pubupdate;
 	int			i_pubdelete;
 	int			i_pubtruncate;
+	int			i_pubddl_table;
 	int			i_pubviaroot;
 	int			i,
 				ntups;
@@ -4062,23 +4064,29 @@ getPublications(Archive *fout, int *numPublications)
 	resetPQExpBuffer(query);
 
 	/* Get the publications. */
-	if (fout->remoteVersion >= 130000)
+	if (fout->remoteVersion >= 160000)
 		appendPQExpBufferStr(query,
 							 "SELECT p.tableoid, p.oid, p.pubname, "
 							 "p.pubowner, "
-							 "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+							 "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubddl_table, p.pubviaroot "
+							 "FROM pg_publication p");
+	else if (fout->remoteVersion >= 130000)
+		appendPQExpBufferStr(query,
+							 "SELECT p.tableoid, p.oid, p.pubname, "
+							 "p.pubowner, "
+							 "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, p.pubviaroot "
 							 "FROM pg_publication p");
 	else if (fout->remoteVersion >= 110000)
 		appendPQExpBufferStr(query,
 							 "SELECT p.tableoid, p.oid, p.pubname, "
 							 "p.pubowner, "
-							 "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
+							 "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false AS pubviaroot "
 							 "FROM pg_publication p");
 	else
 		appendPQExpBufferStr(query,
 							 "SELECT p.tableoid, p.oid, p.pubname, "
 							 "p.pubowner, "
-							 "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubviaroot "
+							 "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false as p.pubddl_table, false AS pubviaroot "
 							 "FROM pg_publication p");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4094,6 +4102,7 @@ getPublications(Archive *fout, int *numPublications)
 	i_pubupdate = PQfnumber(res, "pubupdate");
 	i_pubdelete = PQfnumber(res, "pubdelete");
 	i_pubtruncate = PQfnumber(res, "pubtruncate");
+	i_pubddl_table = PQfnumber(res, "pubddl_table");
 	i_pubviaroot = PQfnumber(res, "pubviaroot");
 
 	pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4117,6 +4126,8 @@ getPublications(Archive *fout, int *numPublications)
 			(strcmp(PQgetvalue(res, i, i_pubdelete), "t") == 0);
 		pubinfo[i].pubtruncate =
 			(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
+		pubinfo[i].pubddl_table =
+			(strcmp(PQgetvalue(res, i, i_pubddl_table), "t") == 0);
 		pubinfo[i].pubviaroot =
 			(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
 
@@ -4196,7 +4207,10 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
 		first = false;
 	}
 
-	appendPQExpBufferChar(query, '\'');
+	appendPQExpBufferStr(query, "'");
+
+	if (pubinfo->pubddl_table)
+		appendPQExpBufferStr(query, ", ddl = 'table'");
 
 	if (pubinfo->pubviaroot)
 		appendPQExpBufferStr(query, ", publish_via_partition_root = true");
@@ -7992,6 +8006,7 @@ getEventTriggers(Archive *fout, int *numEventTriggers)
 
 	query = createPQExpBuffer();
 
+	/*Skip internally created event triggers by checking evtisinternal */
 	appendPQExpBufferStr(query,
 						 "SELECT e.tableoid, e.oid, evtname, evtenabled, "
 						 "evtevent, evtowner, "
@@ -8000,6 +8015,7 @@ getEventTriggers(Archive *fout, int *numEventTriggers)
 						 " from unnest(evttags) as t(x)), ', ') as evttags, "
 						 "e.evtfoid::regproc as evtfname "
 						 "FROM pg_event_trigger e "
+						 "WHERE NOT e.evtisinternal "
 						 "ORDER BY e.oid");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index bc8f2ec36d..7673d153aa 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -621,6 +621,7 @@ typedef struct _PublicationInfo
 	bool		pubupdate;
 	bool		pubdelete;
 	bool		pubtruncate;
+	bool		pubddl_table;
 	bool		pubviaroot;
 } PublicationInfo;
 
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 15852188c4..5acbfaeb8b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2912,7 +2912,7 @@ my %tests = (
 		create_order => 50,
 		create_sql => 'CREATE PUBLICATION pub2
 						 FOR ALL TABLES
-						 WITH (publish = \'\');',
+						 WITH (publish = \'\', ddl = \'\');',
 		regexp => qr/^
 			\QCREATE PUBLICATION pub2 FOR ALL TABLES WITH (publish = '');\E
 			/xm,
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..4df5660b51 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,6 +10,7 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/logicalddlmsgdesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..daf1730252 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -26,6 +26,7 @@
 #include "commands/dbcommands_xlog.h"
 #include "commands/sequence.h"
 #include "commands/tablespace.h"
+#include "replication/ddlmessage.h"
 #include "replication/message.h"
 #include "replication/origin.h"
 #include "rmgrdesc.h"
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 9325a46b8f..750ea19dc1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6180,7 +6180,7 @@ listPublications(const char *pattern)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6207,6 +6207,10 @@ listPublications(const char *pattern)
 					  gettext_noop("Inserts"),
 					  gettext_noop("Updates"),
 					  gettext_noop("Deletes"));
+	if (pset.sversion >= 160000)
+		appendPQExpBuffer(&buf,
+						  ",\n  pubddl_table AS \"%s\"",
+						  gettext_noop("Table DDLs"));
 	if (pset.sversion >= 110000)
 		appendPQExpBuffer(&buf,
 						  ",\n  pubtruncate AS \"%s\"",
@@ -6305,6 +6309,7 @@ describePublications(const char *pattern)
 	PGresult   *res;
 	bool		has_pubtruncate;
 	bool		has_pubviaroot;
+	bool		has_pubddl;
 
 	PQExpBufferData title;
 	printTableContent cont;
@@ -6319,6 +6324,7 @@ describePublications(const char *pattern)
 		return true;
 	}
 
+	has_pubddl =  (pset.sversion >= 160000);
 	has_pubtruncate = (pset.sversion >= 110000);
 	has_pubviaroot = (pset.sversion >= 130000);
 
@@ -6328,6 +6334,9 @@ describePublications(const char *pattern)
 					  "SELECT oid, pubname,\n"
 					  "  pg_catalog.pg_get_userbyid(pubowner) AS owner,\n"
 					  "  puballtables, pubinsert, pubupdate, pubdelete");
+	if (has_pubddl)
+		appendPQExpBufferStr(&buf,
+							 ", pubddl_table");
 	if (has_pubtruncate)
 		appendPQExpBufferStr(&buf,
 							 ", pubtruncate");
@@ -6381,6 +6390,8 @@ describePublications(const char *pattern)
 		bool		puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
 		printTableOpt myopt = pset.popt.topt;
 
+		if (has_pubddl)
+			ncols++;
 		if (has_pubtruncate)
 			ncols++;
 		if (has_pubviaroot)
@@ -6395,6 +6406,8 @@ describePublications(const char *pattern)
 		printTableAddHeader(&cont, gettext_noop("Inserts"), true, align);
 		printTableAddHeader(&cont, gettext_noop("Updates"), true, align);
 		printTableAddHeader(&cont, gettext_noop("Deletes"), true, align);
+		if (has_pubddl)
+			printTableAddHeader(&cont, gettext_noop("Table DDLs"), true, align);
 		if (has_pubtruncate)
 			printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
 		if (has_pubviaroot)
@@ -6405,10 +6418,12 @@ describePublications(const char *pattern)
 		printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false);
 		printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false);
 		printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false);
-		if (has_pubtruncate)
+		if (has_pubddl)
 			printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
-		if (has_pubviaroot)
+		if (has_pubtruncate)
 			printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+		if (has_pubviaroot)
+			printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
 
 		if (!puballtables)
 		{
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 463bcb67c5..abcbe97593 100644
--- a/src/include/access/rmgrlist.h
+++ b/src/include/access/rmgrlist.h
@@ -47,3 +47,4 @@ PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_i
 PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL, NULL, NULL)
 PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL, generic_mask, NULL)
 PG_RMGR(RM_LOGICALMSG_ID, "LogicalMessage", logicalmsg_redo, logicalmsg_desc, logicalmsg_identify, NULL, NULL, NULL, logicalmsg_decode)
+PG_RMGR(RM_LOGICALDDLMSG_ID, "LogicalDDLMessage", logicalddlmsg_redo, logicalddlmsg_desc, logicalddlmsg_identify, NULL, NULL, NULL, logicalddl_decode)
diff --git a/src/include/catalog/pg_event_trigger.h b/src/include/catalog/pg_event_trigger.h
index e30550f6e1..f8f00e88fa 100644
--- a/src/include/catalog/pg_event_trigger.h
+++ b/src/include/catalog/pg_event_trigger.h
@@ -36,6 +36,7 @@ CATALOG(pg_event_trigger,3466,EventTriggerRelationId)
 												 * called */
 	char		evtenabled;		/* trigger's firing configuration WRT
 								 * session_replication_role */
+	bool		evtisinternal;	/* trigger is system-generated */
 
 #ifdef CATALOG_VARLEN
 	text		evttags[1];		/* command TAGs this event trigger targets */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 76ff23b779..014116ff9e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11145,6 +11145,14 @@
   proname => 'pg_logical_emit_message', provolatile => 'v', proparallel => 'u',
   prorettype => 'pg_lsn', proargtypes => 'bool text bytea',
   prosrc => 'pg_logical_emit_message_bytea' },
+{ oid => '3813', descr => 'emit a textual logical decoding message',
+  proname => 'pg_logical_emit_ddl_message', provolatile => 'v', proparallel => 'u',
+  prorettype => 'pg_lsn', proargtypes => 'bool text text',
+  prosrc => 'pg_logical_emit_ddl_message_text' },
+{ oid => '3814', descr => 'emit a binary logical decoding message',
+  proname => 'pg_logical_emit_ddl_message', provolatile => 'v', proparallel => 'u',
+  prorettype => 'pg_lsn', proargtypes => 'text regclass int4 text',
+  prosrc => 'pg_logical_emit_ddl_message_bytea' },
 
 # event triggers
 { oid => '3566', descr => 'list objects dropped by the current command',
@@ -12049,5 +12057,17 @@
 { oid => '4643', descr => 'expand JSON format DDL to a plain text DDL command',
   proname => 'ddl_deparse_expand_command', prorettype => 'text',
   proargtypes => 'text', prosrc => 'ddl_deparse_expand_command' },
+{ oid => '4644', descr => 'trigger for ddl command deparse end',
+  proname => 'publication_deparse_ddl_command_end', prorettype => 'event_trigger',
+  proargtypes => '', prosrc => 'publication_deparse_ddl_command_end' },
+{ oid => '4645', descr => 'trigger for ddl command deparse start',
+  proname => 'publication_deparse_ddl_command_start', prorettype => 'event_trigger',
+  proargtypes => '', prosrc => 'publication_deparse_ddl_command_start' },
+{ oid => '4646', descr => 'trigger for ddl command deparse table rewrite',
+  proname => 'publication_deparse_table_rewrite', prorettype => 'event_trigger',
+  proargtypes => '', prosrc => 'publication_deparse_table_rewrite' },
+{ oid => '4647', descr => 'trigger for ddl command deparse table init',
+  proname => 'publication_deparse_table_init_write', prorettype => 'event_trigger',
+  proargtypes => '', prosrc => 'publication_deparse_table_init_write' },
 
 ]
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 6ecaa2a01e..0d8fe84bf9 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,8 +18,17 @@
 #define PG_PUBLICATION_H
 
 #include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
 #include "catalog/pg_publication_d.h"
+#include "nodes/pg_list.h"
+
+/* Publication trigger events */
+#define PUB_TRIG_DDL_CMD_START "ddl_command_start"
+#define PUB_TRIG_DDL_CMD_END "ddl_command_end"
+#define PUB_TRIG_TBL_REWRITE "table_rewrite"
+#define PUB_TRIG_TBL_INIT_WRITE "table_init_write"
+
+/* Publication event trigger prefix */
+#define PUB_EVENT_TRIG_FORMAT "pg_deparse_trig_%s_%u"
 
 /* ----------------
  *		pg_publication definition.  cpp turns this into
@@ -54,6 +63,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
 
 	/* true if partition changes are published using root schema */
 	bool		pubviaroot;
+
+	/* true if table ddls are published */
+	bool		pubddl_table;
 } FormData_pg_publication;
 
 /* ----------------
@@ -72,6 +84,7 @@ typedef struct PublicationActions
 	bool		pubupdate;
 	bool		pubdelete;
 	bool		pubtruncate;
+	bool		pubddl_table;
 } PublicationActions;
 
 typedef struct PublicationDesc
@@ -103,13 +116,6 @@ typedef struct Publication
 	PublicationActions pubactions;
 } Publication;
 
-typedef struct PublicationRelInfo
-{
-	Relation	relation;
-	Node	   *whereClause;
-	List	   *columns;
-} PublicationRelInfo;
-
 extern Publication *GetPublication(Oid pubid);
 extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
 extern List *GetRelationPublications(Oid relid);
@@ -145,14 +151,6 @@ extern List *GetPubPartitionOptionRelations(List *result,
 extern Oid	GetTopMostAncestorInPublication(Oid puboid, List *ancestors,
 											int *ancestor_level);
 
-extern bool is_publishable_relation(Relation rel);
 extern bool is_schema_publication(Oid pubid);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
-											  bool if_not_exists);
-extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
-											bool if_not_exists);
-
-extern Bitmapset *pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols,
-										   MemoryContext mcxt);
 
 #endif							/* PG_PUBLICATION_H */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index cba4e72455..208a69408f 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -73,6 +73,13 @@ typedef struct SQLDropObject
 #define AT_REWRITE_COLUMN_REWRITE		0x04
 #define AT_REWRITE_ACCESS_METHOD		0x08
 
+/* Trigger events */
+#define TRIG_DDL_CMD_START "ddl_command_start"
+#define TRIG_DDL_CMD_END "ddl_command_end"
+#define TRIG_TBL_REWRITE "table_rewrite"
+#define TRIG_TBL_INIT_WRITE "table_init_write"
+#define TRIG_TBL_CMD_DROP "sql_drop"
+
 /*
  * EventTriggerData is the node type that is passed as fmgr "context" info
  * when a function is called by the event trigger manager.
@@ -80,7 +87,7 @@ typedef struct SQLDropObject
 #define CALLED_AS_EVENT_TRIGGER(fcinfo) \
 	((fcinfo)->context != NULL && IsA((fcinfo)->context, EventTriggerData))
 
-extern Oid	CreateEventTrigger(CreateEventTrigStmt *stmt);
+extern Oid	CreateEventTrigger(CreateEventTrigStmt *stmt, bool is_internal);
 extern Oid	get_event_trigger_oid(const char *trigname, bool missing_ok);
 
 extern Oid	AlterEventTrigger(AlterEventTrigStmt *stmt);
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 70d5e3680a..a3fca9e64c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -22,6 +22,13 @@
 /* Same as MAXNUMMESSAGES in sinvaladt.c */
 #define MAX_RELCACHE_INVAL_MSGS 4096
 
+typedef struct PublicationRelInfo
+{
+	Relation	relation;
+	Node	   *whereClause;
+	List	   *columns;
+} PublicationRelInfo;
+
 extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
 extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
 extern void RemovePublicationById(Oid pubid);
@@ -35,5 +42,12 @@ extern bool pub_rf_contains_invalid_column(Oid pubid, Relation relation,
 										   List *ancestors, bool pubviaroot);
 extern bool pub_collist_contains_invalid_column(Oid pubid, Relation relation,
 												List *ancestors, bool pubviaroot);
+extern Bitmapset *pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols,
+										   MemoryContext mcxt);
+extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
+											  bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+											bool if_not_exists);
+extern bool is_publishable_relation(Relation rel);
 
 #endif							/* PUBLICATIONCMDS_H */
diff --git a/src/include/replication/ddlmessage.h b/src/include/replication/ddlmessage.h
new file mode 100644
index 0000000000..e530ea6d26
--- /dev/null
+++ b/src/include/replication/ddlmessage.h
@@ -0,0 +1,60 @@
+/*-------------------------------------------------------------------------
+ * ddlmessage.h
+ *	   Exports from replication/logical/ddlmessage.c
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/replication/ddlmessage.h
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_LOGICAL_DDL_MESSAGE_H
+#define PG_LOGICAL_DDL_MESSAGE_H
+
+#include "access/xlog.h"
+#include "access/xlogdefs.h"
+#include "access/xlogreader.h"
+#include "nodes/nodes.h"
+
+
+/*
+ * Support for keeping track of deparsed commands.
+ */
+typedef enum DeparsedCommandType
+{
+	DCT_ObjectCreate,
+	DCT_ObjectDrop,
+	DCT_SimpleCmd,
+	DCT_TableAlter,
+	DCT_TableDropEnd,
+	DCT_TableDropStart
+} DeparsedCommandType;
+
+/*
+ * Generic logical decoding DDL message wal record.
+ */
+typedef struct xl_logical_ddl_message
+{
+	Oid			dbId;			/* database Oid emitted from */
+	Size		prefix_size;	/* length of prefix including null terminator */
+	Oid			relid;			/* id of the table */
+	DeparsedCommandType cmdtype;	/* type of sql command */
+	Size		message_size;	/* size of the message */
+
+	/*
+	 * payload, including null-terminated prefix of length prefix_size
+	 */
+	char		message[FLEXIBLE_ARRAY_MEMBER];
+} xl_logical_ddl_message;
+
+#define SizeOfLogicalDDLMessage	(offsetof(xl_logical_ddl_message, message))
+
+extern XLogRecPtr LogLogicalDDLMessage(const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+									   const char *ddl_message, size_t size);
+
+/* RMGR API*/
+#define XLOG_LOGICAL_DDL_MESSAGE	0x00
+void		logicalddlmsg_redo(XLogReaderState *record);
+void		logicalddlmsg_desc(StringInfo buf, XLogReaderState *record);
+const char *logicalddlmsg_identify(uint8 info);
+
+#endif
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 14fa921ab4..c9ac708d32 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -27,6 +27,7 @@ extern void heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
 extern void xact_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
 extern void standby_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
 extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+extern void logicalddl_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
 
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
diff --git a/src/include/replication/logicalproto.h b/src/include/replication/logicalproto.h
index 0ea2df5088..5fb3baaeb1 100644
--- a/src/include/replication/logicalproto.h
+++ b/src/include/replication/logicalproto.h
@@ -66,6 +66,7 @@ typedef enum LogicalRepMsgType
 	LOGICAL_REP_MSG_RELATION = 'R',
 	LOGICAL_REP_MSG_TYPE = 'Y',
 	LOGICAL_REP_MSG_MESSAGE = 'M',
+	LOGICAL_REP_MSG_DDL = 'L',
 	LOGICAL_REP_MSG_BEGIN_PREPARE = 'b',
 	LOGICAL_REP_MSG_PREPARE = 'P',
 	LOGICAL_REP_MSG_COMMIT_PREPARED = 'K',
@@ -246,6 +247,9 @@ extern List *logicalrep_read_truncate(StringInfo in,
 									  bool *cascade, bool *restart_seqs);
 extern void logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn,
 									 bool transactional, const char *prefix, Size sz, const char *message);
+extern void logicalrep_write_ddl(StringInfo out, XLogRecPtr lsn,
+										const char *prefix, Size sz, const char *message);
+extern char *logicalrep_read_ddl(StringInfo in, XLogRecPtr *lsn, const char **prefix, Size *sz);
 extern void logicalrep_write_rel(StringInfo out, TransactionId xid,
 								 Relation rel, Bitmapset *columns);
 extern LogicalRepRelation *logicalrep_read_rel(StringInfo in);
diff --git a/src/include/replication/output_plugin.h b/src/include/replication/output_plugin.h
index 3ac6729386..2ad96d374a 100644
--- a/src/include/replication/output_plugin.h
+++ b/src/include/replication/output_plugin.h
@@ -90,6 +90,18 @@ typedef void (*LogicalDecodeMessageCB) (struct LogicalDecodingContext *ctx,
 										Size message_size,
 										const char *message);
 
+/*
+ * Callback for the logical decoding DDL messages.
+ */
+typedef void (*LogicalDecodeDDLMessageCB) (struct LogicalDecodingContext *ctx,
+										   ReorderBufferTXN *txn,
+										   XLogRecPtr message_lsn,
+										   const char *prefix,
+										   Oid relid,
+										   DeparsedCommandType cmdtype,
+										   Size message_size,
+										   const char *message);
+
 /*
  * Filter changes by origin.
  */
@@ -201,6 +213,19 @@ typedef void (*LogicalDecodeStreamMessageCB) (struct LogicalDecodingContext *ctx
 											  Size message_size,
 											  const char *message);
 
+/*
+ * Callback for streaming logical decoding DDL messages from in-progress
+ * transactions.
+ */
+typedef void (*LogicalDecodeStreamDDLMessageCB) (struct LogicalDecodingContext *ctx,
+												 ReorderBufferTXN *txn,
+												 XLogRecPtr message_lsn,
+												 const char *prefix,
+												 Oid relid,
+												 DeparsedCommandType cmdtype,
+												 Size message_size,
+												 const char *message);
+
 /*
  * Callback for streaming truncates from in-progress transactions.
  */
@@ -221,6 +246,7 @@ typedef struct OutputPluginCallbacks
 	LogicalDecodeTruncateCB truncate_cb;
 	LogicalDecodeCommitCB commit_cb;
 	LogicalDecodeMessageCB message_cb;
+	LogicalDecodeDDLMessageCB ddl_cb;
 	LogicalDecodeFilterByOriginCB filter_by_origin_cb;
 	LogicalDecodeShutdownCB shutdown_cb;
 
@@ -239,6 +265,7 @@ typedef struct OutputPluginCallbacks
 	LogicalDecodeStreamCommitCB stream_commit_cb;
 	LogicalDecodeStreamChangeCB stream_change_cb;
 	LogicalDecodeStreamMessageCB stream_message_cb;
+	LogicalDecodeStreamDDLMessageCB stream_ddl_cb;
 	LogicalDecodeStreamTruncateCB stream_truncate_cb;
 } OutputPluginCallbacks;
 
diff --git a/src/include/replication/pgoutput.h b/src/include/replication/pgoutput.h
index b4a8015403..a2cf99b4e4 100644
--- a/src/include/replication/pgoutput.h
+++ b/src/include/replication/pgoutput.h
@@ -25,6 +25,7 @@ typedef struct PGOutputData
 	uint32		protocol_version;
 	List	   *publication_names;
 	List	   *publications;
+	List	   *deleted_relids;
 	bool		binary;
 	char		streaming;
 	bool		messages;
diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h
index 1b9db22acb..2b37c96c1f 100644
--- a/src/include/replication/reorderbuffer.h
+++ b/src/include/replication/reorderbuffer.h
@@ -11,6 +11,8 @@
 
 #include "access/htup_details.h"
 #include "lib/ilist.h"
+#include "nodes/nodes.h"
+#include "replication/ddlmessage.h"
 #include "storage/sinval.h"
 #include "utils/hsearch.h"
 #include "utils/relcache.h"
@@ -66,6 +68,7 @@ typedef enum ReorderBufferChangeType
 	REORDER_BUFFER_CHANGE_UPDATE,
 	REORDER_BUFFER_CHANGE_DELETE,
 	REORDER_BUFFER_CHANGE_MESSAGE,
+	REORDER_BUFFER_CHANGE_DDL,
 	REORDER_BUFFER_CHANGE_INVALIDATION,
 	REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT,
 	REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID,
@@ -139,6 +142,16 @@ typedef struct ReorderBufferChange
 			char	   *message;
 		}			msg;
 
+		/* DDL */
+		struct
+		{
+			char	   *prefix;
+			Size		message_size;
+			char	   *message;
+			Oid			relid;
+			DeparsedCommandType cmdtype;
+		}			ddl;
+
 		/* New snapshot, set when action == *_INTERNAL_SNAPSHOT */
 		Snapshot	snapshot;
 
@@ -470,6 +483,16 @@ typedef void (*ReorderBufferMessageCB) (ReorderBuffer *rb,
 										const char *prefix, Size sz,
 										const char *message);
 
+/* DDL message callback signature */
+typedef void (*ReorderBufferDDLMessageCB) (ReorderBuffer *rb,
+										   ReorderBufferTXN *txn,
+										   XLogRecPtr message_lsn,
+										   const char *prefix,
+										   Oid relid,
+										   DeparsedCommandType cmdtype,
+										   Size sz,
+										   const char *message);
+
 /* begin prepare callback signature */
 typedef void (*ReorderBufferBeginPrepareCB) (ReorderBuffer *rb,
 											 ReorderBufferTXN *txn);
@@ -536,6 +559,17 @@ typedef void (*ReorderBufferStreamMessageCB) (
 											  const char *prefix, Size sz,
 											  const char *message);
 
+/* stream DDL message callback signature */
+typedef void (*ReorderBufferStreamDDLMessageCB) (
+												 ReorderBuffer *rb,
+												 ReorderBufferTXN *txn,
+												 XLogRecPtr message_lsn,
+												 const char *prefix,
+												 Oid relid,
+												 DeparsedCommandType cmdtype,
+												 Size sz,
+												 const char *message);
+
 /* stream truncate callback signature */
 typedef void (*ReorderBufferStreamTruncateCB) (
 											   ReorderBuffer *rb,
@@ -592,6 +626,7 @@ struct ReorderBuffer
 	ReorderBufferApplyTruncateCB apply_truncate;
 	ReorderBufferCommitCB commit;
 	ReorderBufferMessageCB message;
+	ReorderBufferDDLMessageCB ddl;
 
 	/*
 	 * Callbacks to be called when streaming a transaction at prepare time.
@@ -611,6 +646,7 @@ struct ReorderBuffer
 	ReorderBufferStreamCommitCB stream_commit;
 	ReorderBufferStreamChangeCB stream_change;
 	ReorderBufferStreamMessageCB stream_message;
+	ReorderBufferStreamDDLMessageCB stream_ddl;
 	ReorderBufferStreamTruncateCB stream_truncate;
 
 	/*
@@ -696,6 +732,9 @@ extern void ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
 									  Snapshot snap, XLogRecPtr lsn,
 									  bool transactional, const char *prefix,
 									  Size message_size, const char *message);
+extern void ReorderBufferQueueDDLMessage(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
+										 const char *prefix, Size message_size,
+										 const char *message, Oid relid, DeparsedCommandType cmdtype);
 extern void ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
 								XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
 								TimestampTz commit_time, RepOriginId origin_id, XLogRecPtr origin_lsn);
diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h
index 1e7514dcff..32ccc156d0 100644
--- a/src/include/tcop/cmdtag.h
+++ b/src/include/tcop/cmdtag.h
@@ -16,7 +16,7 @@
 /* buffer size required for command completion tags */
 #define COMPLETION_TAG_BUFSIZE	64
 
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, ddl_repl_ok) \
 	tag,
 
 typedef enum CommandTag
@@ -53,9 +53,11 @@ CopyQueryCompletion(QueryCompletion *dst, const QueryCompletion *src)
 extern void InitializeQueryCompletion(QueryCompletion *qc);
 extern const char *GetCommandTagName(CommandTag commandTag);
 extern const char *GetCommandTagNameAndLen(CommandTag commandTag, Size *len);
+extern CommandTag *GetCommandTagsForDDLRepl(int *ncommands);
 extern bool command_tag_display_rowcount(CommandTag commandTag);
 extern bool command_tag_event_trigger_ok(CommandTag commandTag);
 extern bool command_tag_table_rewrite_ok(CommandTag commandTag);
+extern bool command_tag_ddl_replication_ok(CommandTag commandTag);
 extern CommandTag GetCommandTagEnum(const char *commandname);
 extern Size BuildQueryCompletionString(char *buff, const QueryCompletion *qc,
 									   bool nameonly);
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index e738ac1c09..7f44a150eb 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -23,196 +23,196 @@
  * textual name, so that we can bsearch on it; see GetCommandTagEnum().
  */
 
-/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount */
-PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
-PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
-PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false)
-PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false)
-PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false)
-PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false)
-PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false)
-PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true)
-PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true)
-PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
-PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
-PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
-PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true)
-PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
-PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
-PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
-PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_MERGE, "MERGE", false, false, true)
-PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true)
-PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false)
-PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false)
-PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false)
-PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false)
-PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false)
-PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false)
-PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true)
-PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false)
-PG_CMDTAG(CMDTAG_SET, "SET", false, false, false)
-PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false)
-PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false)
-PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
-PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount, ddl_repl_ok */
+PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, true, false, true)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false, true)
+PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false, false)
+PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false, false)
+PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false, false)
+PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false, true)
+PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false, false)
+PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false, false)
+PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true, false)
+PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false, false)
+PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true, false)
+PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false, false)
+PG_CMDTAG(CMDTAG_DO, "DO", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false, false)
+PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false, false)
+PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true, false)
+PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false, true)
+PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true, false)
+PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false, false)
+PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false, false)
+PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_MERGE, "MERGE", false, false, true, false)
+PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true, false)
+PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false, false)
+PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false, false)
+PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false, false)
+PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false, false)
+PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false, false)
+PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false, false)
+PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false, true)
+PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false, false)
+PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false, false)
+PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false, false)
+PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false, true)
+PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false, false)
+PG_CMDTAG(CMDTAG_SET, "SET", false, false, false, false)
+PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false, false)
+PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false, false)
+PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false, false)
+PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false, false)
+PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true, false)
+PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false, false)
diff --git a/src/include/tcop/ddldeparse.h b/src/include/tcop/ddldeparse.h
index a712060246..b303956568 100644
--- a/src/include/tcop/ddldeparse.h
+++ b/src/include/tcop/ddldeparse.h
@@ -2,8 +2,7 @@
  *
  * ddldeparse.h
  *
- * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
+ * Portions Copyright (c) 2023, PostgreSQL Global Development Group
  *
  * src/include/tcop/ddldeparse.h
  *
@@ -14,7 +13,15 @@
 
 #include "tcop/deparse_utility.h"
 
-extern char *deparse_utility_command(CollectedCommand *cmd, bool verbose_mode);
+/* Context info needed for deparsing ddl command */
+typedef struct
+{
+	bool		verbose_mode;
+	/* The maximum volatility of functions in expressions of a DDL command. */
+	char		max_volatility;
+} ddl_deparse_context;
+
+extern char *deparse_utility_command(CollectedCommand *cmd, ddl_deparse_context *context);
 extern char *deparse_ddl_json_to_string(char *jsonb);
 extern char *deparse_drop_command(const char *objidentity, const char *objecttype,
 								  Node *parsetree);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 1426a353cd..a656f5bd7c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -17,10 +17,12 @@
 #include "access/tupdesc.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
+#include "catalog/objectaddress.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
 #include "catalog/pg_publication.h"
 #include "nodes/bitmapset.h"
+#include "nodes/lockoptions.h"
 #include "partitioning/partdefs.h"
 #include "rewrite/prs2lock.h"
 #include "storage/block.h"
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 8f93028363..008f610d11 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -6223,9 +6223,9 @@ List of schemas
 (0 rows)
 
 \dRp "no.such.publication"
-                              List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root 
-------+-------+------------+---------+---------+---------+-----------+----------
+                                    List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+------+-------+------------+---------+---------+---------+------------+-----------+----------
 (0 rows)
 
 \dRs "no.such.subscription"
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 69dc6cfd85..6a5af2fd3f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -30,20 +30,20 @@ ERROR:  conflicting or redundant options
 LINE 1: ...ub_xxx WITH (publish_via_partition_root = 'true', publish_vi...
                                                              ^
 \dRp
-                                              List of publications
-        Name        |          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f          | t       | f       | f       | f         | f
- testpub_default    | regress_publication_user | f          | f       | t       | f       | f         | f
+                                                     List of publications
+        Name        |          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f          | t       | f       | f       | f          | f         | f
+ testpub_default    | regress_publication_user | f          | f       | t       | f       | f          | f         | f
 (2 rows)
 
 ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
 \dRp
-                                              List of publications
-        Name        |          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f          | t       | f       | f       | f         | f
- testpub_default    | regress_publication_user | f          | t       | t       | t       | f         | f
+                                                     List of publications
+        Name        |          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f          | t       | f       | f       | f          | f         | f
+ testpub_default    | regress_publication_user | f          | t       | t       | t       | f          | f         | f
 (2 rows)
 
 --- adding tables
@@ -87,10 +87,10 @@ RESET client_min_messages;
 -- should be able to add schema to 'FOR TABLE' publication
 ALTER PUBLICATION testpub_fortable ADD TABLES IN SCHEMA pub_test;
 \dRp+ testpub_fortable
-                                Publication testpub_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                      Publication testpub_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "public.testpub_tbl1"
 Tables from schemas:
@@ -99,20 +99,20 @@ Tables from schemas:
 -- should be able to drop schema from 'FOR TABLE' publication
 ALTER PUBLICATION testpub_fortable DROP TABLES IN SCHEMA pub_test;
 \dRp+ testpub_fortable
-                                Publication testpub_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                      Publication testpub_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "public.testpub_tbl1"
 
 -- should be able to set schema to 'FOR TABLE' publication
 ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test;
 \dRp+ testpub_fortable
-                                Publication testpub_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                      Publication testpub_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test"
 
@@ -123,10 +123,10 @@ CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test;
 CREATE PUBLICATION testpub_for_tbl_schema FOR TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
 RESET client_min_messages;
 \dRp+ testpub_for_tbl_schema
-                             Publication testpub_for_tbl_schema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                   Publication testpub_for_tbl_schema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "pub_test.testpub_nopk"
 Tables from schemas:
@@ -144,10 +144,10 @@ LINE 1: ...CATION testpub_parsertst FOR TABLES IN SCHEMA foo, test.foo;
 -- should be able to add a table of the same schema to the schema publication
 ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
 \dRp+ testpub_forschema
-                               Publication testpub_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                      Publication testpub_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "pub_test.testpub_nopk"
 Tables from schemas:
@@ -156,10 +156,10 @@ Tables from schemas:
 -- should be able to drop the table
 ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
 \dRp+ testpub_forschema
-                               Publication testpub_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                      Publication testpub_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test"
 
@@ -170,10 +170,10 @@ ERROR:  relation "testpub_nopk" is not part of the publication
 -- should be able to set table to schema publication
 ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
 \dRp+ testpub_forschema
-                               Publication testpub_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                      Publication testpub_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "pub_test.testpub_nopk"
 
@@ -195,10 +195,10 @@ Publications:
     "testpub_foralltables"
 
 \dRp+ testpub_foralltables
-                              Publication testpub_foralltables
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | t          | t       | t       | f       | f         | f
+                                    Publication testpub_foralltables
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | t          | t       | t       | f       | f          | f         | f
 (1 row)
 
 DROP TABLE testpub_tbl2;
@@ -210,19 +210,19 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3;
 CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3;
 RESET client_min_messages;
 \dRp+ testpub3
-                                    Publication testpub3
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                          Publication testpub3
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "public.testpub_tbl3"
     "public.testpub_tbl3a"
 
 \dRp+ testpub4
-                                    Publication testpub4
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                          Publication testpub4
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "public.testpub_tbl3"
 
@@ -243,10 +243,10 @@ UPDATE testpub_parted1 SET a = 1;
 -- only parent is listed as being in publication, not the partition
 ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted;
 \dRp+ testpub_forparted
-                               Publication testpub_forparted
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                      Publication testpub_forparted
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "public.testpub_parted"
 
@@ -261,10 +261,10 @@ ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
 UPDATE testpub_parted1 SET a = 1;
 ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true);
 \dRp+ testpub_forparted
-                               Publication testpub_forparted
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | t
+                                      Publication testpub_forparted
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | t
 Tables:
     "public.testpub_parted"
 
@@ -293,10 +293,10 @@ SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub5 FOR TABLE testpub_rf_tbl1, testpub_rf_tbl2 WHERE (c <> 'test' AND d < 5) WITH (publish = 'insert');
 RESET client_min_messages;
 \dRp+ testpub5
-                                    Publication testpub5
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                          Publication testpub5
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | f       | f       | f          | f         | f
 Tables:
     "public.testpub_rf_tbl1"
     "public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -309,10 +309,10 @@ Tables:
 
 ALTER PUBLICATION testpub5 ADD TABLE testpub_rf_tbl3 WHERE (e > 1000 AND e < 2000);
 \dRp+ testpub5
-                                    Publication testpub5
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                          Publication testpub5
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | f       | f       | f          | f         | f
 Tables:
     "public.testpub_rf_tbl1"
     "public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -328,10 +328,10 @@ Publications:
 
 ALTER PUBLICATION testpub5 DROP TABLE testpub_rf_tbl2;
 \dRp+ testpub5
-                                    Publication testpub5
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                          Publication testpub5
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | f       | f       | f          | f         | f
 Tables:
     "public.testpub_rf_tbl1"
     "public.testpub_rf_tbl3" WHERE ((e > 1000) AND (e < 2000))
@@ -339,10 +339,10 @@ Tables:
 -- remove testpub_rf_tbl1 and add testpub_rf_tbl3 again (another WHERE expression)
 ALTER PUBLICATION testpub5 SET TABLE testpub_rf_tbl3 WHERE (e > 300 AND e < 500);
 \dRp+ testpub5
-                                    Publication testpub5
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                          Publication testpub5
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | f       | f       | f          | f         | f
 Tables:
     "public.testpub_rf_tbl3" WHERE ((e > 300) AND (e < 500))
 
@@ -375,10 +375,10 @@ SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub_syntax1 FOR TABLE testpub_rf_tbl1, ONLY testpub_rf_tbl3 WHERE (e < 999) WITH (publish = 'insert');
 RESET client_min_messages;
 \dRp+ testpub_syntax1
-                                Publication testpub_syntax1
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                       Publication testpub_syntax1
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | f       | f       | f          | f         | f
 Tables:
     "public.testpub_rf_tbl1"
     "public.testpub_rf_tbl3" WHERE (e < 999)
@@ -388,10 +388,10 @@ SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub_syntax2 FOR TABLE testpub_rf_tbl1, testpub_rf_schema1.testpub_rf_tbl5 WHERE (h < 999) WITH (publish = 'insert');
 RESET client_min_messages;
 \dRp+ testpub_syntax2
-                                Publication testpub_syntax2
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                       Publication testpub_syntax2
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | f       | f       | f          | f         | f
 Tables:
     "public.testpub_rf_tbl1"
     "testpub_rf_schema1.testpub_rf_tbl5" WHERE (h < 999)
@@ -506,10 +506,10 @@ CREATE PUBLICATION testpub6 FOR TABLES IN SCHEMA testpub_rf_schema2;
 ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpub_rf_schema2.testpub_rf_tbl6 WHERE (i < 99);
 RESET client_min_messages;
 \dRp+ testpub6
-                                    Publication testpub6
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                          Publication testpub6
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "testpub_rf_schema2.testpub_rf_tbl6" WHERE (i < 99)
 Tables from schemas:
@@ -723,10 +723,10 @@ CREATE PUBLICATION testpub_table_ins WITH (publish = 'insert, truncate');
 RESET client_min_messages;
 ALTER PUBLICATION testpub_table_ins ADD TABLE testpub_tbl5 (a);		-- ok
 \dRp+ testpub_table_ins
-                               Publication testpub_table_ins
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | t         | f
+                                      Publication testpub_table_ins
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | f       | f       | f          | t         | f
 Tables:
     "public.testpub_tbl5" (a)
 
@@ -900,10 +900,10 @@ CREATE TABLE testpub_tbl_both_filters (a int, b int, c int, PRIMARY KEY (a,c));
 ALTER TABLE testpub_tbl_both_filters REPLICA IDENTITY USING INDEX testpub_tbl_both_filters_pkey;
 ALTER PUBLICATION testpub_both_filters ADD TABLE testpub_tbl_both_filters (a,c) WHERE (c != 1);
 \dRp+ testpub_both_filters
-                              Publication testpub_both_filters
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                    Publication testpub_both_filters
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "public.testpub_tbl_both_filters" (a, c) WHERE (c <> 1)
 
@@ -1108,10 +1108,10 @@ ERROR:  relation "testpub_tbl1" is already member of publication "testpub_fortbl
 CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1;
 ERROR:  publication "testpub_fortbl" already exists
 \dRp+ testpub_fortbl
-                                 Publication testpub_fortbl
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                       Publication testpub_fortbl
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "pub_test.testpub_nopk"
     "public.testpub_tbl1"
@@ -1149,10 +1149,10 @@ Publications:
     "testpub_fortbl"
 
 \dRp+ testpub_default
-                                Publication testpub_default
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | f         | f
+                                       Publication testpub_default
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | f         | f
 Tables:
     "pub_test.testpub_nopk"
     "public.testpub_tbl1"
@@ -1230,10 +1230,10 @@ REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
 DROP TABLE testpub_parted;
 DROP TABLE testpub_tbl1;
 \dRp+ testpub_default
-                                Publication testpub_default
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | f         | f
+                                       Publication testpub_default
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | f         | f
 (1 row)
 
 -- fail - must be owner of publication
@@ -1243,20 +1243,20 @@ ERROR:  must be owner of publication testpub_default
 RESET ROLE;
 ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
 \dRp testpub_foo
-                                           List of publications
-    Name     |          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
--------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpub_foo | regress_publication_user | f          | t       | t       | t       | f         | f
+                                                 List of publications
+    Name     |          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+-------------+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ testpub_foo | regress_publication_user | f          | t       | t       | t       | f          | f         | f
 (1 row)
 
 -- rename back to keep the rest simple
 ALTER PUBLICATION testpub_foo RENAME TO testpub_default;
 ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
 \dRp testpub_default
-                                             List of publications
-      Name       |           Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
------------------+---------------------------+------------+---------+---------+---------+-----------+----------
- testpub_default | regress_publication_user2 | f          | t       | t       | t       | f         | f
+                                                    List of publications
+      Name       |           Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+-----------------+---------------------------+------------+---------+---------+---------+------------+-----------+----------
+ testpub_default | regress_publication_user2 | f          | t       | t       | t       | f          | f         | f
 (1 row)
 
 -- adding schemas and tables
@@ -1272,19 +1272,19 @@ CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
 SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub1_forschema FOR TABLES IN SCHEMA pub_test1;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
 
 CREATE PUBLICATION testpub2_forschema FOR TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
 \dRp+ testpub2_forschema
-                               Publication testpub2_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub2_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1298,44 +1298,44 @@ CREATE PUBLICATION testpub6_forschema FOR TABLES IN SCHEMA "CURRENT_SCHEMA", CUR
 CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
 RESET client_min_messages;
 \dRp+ testpub3_forschema
-                               Publication testpub3_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub3_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "public"
 
 \dRp+ testpub4_forschema
-                               Publication testpub4_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub4_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "CURRENT_SCHEMA"
 
 \dRp+ testpub5_forschema
-                               Publication testpub5_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub5_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "CURRENT_SCHEMA"
     "public"
 
 \dRp+ testpub6_forschema
-                               Publication testpub6_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub6_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "CURRENT_SCHEMA"
     "public"
 
 \dRp+ testpub_fortable
-                                Publication testpub_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                      Publication testpub_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "CURRENT_SCHEMA.CURRENT_SCHEMA"
 
@@ -1369,10 +1369,10 @@ ERROR:  schema "testpub_view" does not exist
 -- dropping the schema should reflect the change in publication
 DROP SCHEMA pub_test3;
 \dRp+ testpub2_forschema
-                               Publication testpub2_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub2_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1380,20 +1380,20 @@ Tables from schemas:
 -- renaming the schema should reflect the change in publication
 ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
 \dRp+ testpub2_forschema
-                               Publication testpub2_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub2_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1_renamed"
     "pub_test2"
 
 ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
 \dRp+ testpub2_forschema
-                               Publication testpub2_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub2_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1401,10 +1401,10 @@ Tables from schemas:
 -- alter publication add schema
 ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test2;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1413,10 +1413,10 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA non_existent_schema;
 ERROR:  schema "non_existent_schema" does not exist
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1425,10 +1425,10 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test1;
 ERROR:  schema "pub_test1" is already member of publication "testpub1_forschema"
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1436,10 +1436,10 @@ Tables from schemas:
 -- alter publication drop schema
 ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
 
@@ -1447,10 +1447,10 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
 ERROR:  tables from schema "pub_test2" are not part of the publication
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
 
@@ -1458,29 +1458,29 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA non_existent_schema;
 ERROR:  schema "non_existent_schema" does not exist
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
 
 -- drop all schemas
 ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test1;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 (1 row)
 
 -- alter publication set multiple schema
 ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test2;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1489,10 +1489,10 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA non_existent_schema;
 ERROR:  schema "non_existent_schema" does not exist
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1501,10 +1501,10 @@ Tables from schemas:
 -- removing the duplicate schemas
 ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test1;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
 
@@ -1583,18 +1583,18 @@ SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub3_forschema;
 RESET client_min_messages;
 \dRp+ testpub3_forschema
-                               Publication testpub3_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub3_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 (1 row)
 
 ALTER PUBLICATION testpub3_forschema SET TABLES IN SCHEMA pub_test1;
 \dRp+ testpub3_forschema
-                               Publication testpub3_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub3_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables from schemas:
     "pub_test1"
 
@@ -1604,20 +1604,20 @@ CREATE PUBLICATION testpub_forschema_fortable FOR TABLES IN SCHEMA pub_test1, TA
 CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, TABLES IN SCHEMA pub_test1;
 RESET client_min_messages;
 \dRp+ testpub_forschema_fortable
-                           Publication testpub_forschema_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                 Publication testpub_forschema_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "pub_test2.tbl1"
 Tables from schemas:
     "pub_test1"
 
 \dRp+ testpub_fortable_forschema
-                           Publication testpub_fortable_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                 Publication testpub_fortable_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root 
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f          | t       | t       | t       | f          | t         | f
 Tables:
     "pub_test2.tbl1"
 Tables from schemas:
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 87c147dfdb..f84559029b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -566,6 +566,7 @@ DefElemAction
 DefaultACLInfo
 DefineStmt
 DeleteStmt
+DeparsedCommandType
 DependencyGenerator
 DependencyGeneratorData
 DependencyType
@@ -1461,6 +1462,7 @@ LogicalDecodeBeginPrepareCB
 LogicalDecodeChangeCB
 LogicalDecodeCommitCB
 LogicalDecodeCommitPreparedCB
+LogicalDecodeDDLMessageCB
 LogicalDecodeFilterByOriginCB
 LogicalDecodeFilterPrepareCB
 LogicalDecodeMessageCB
@@ -1471,6 +1473,7 @@ LogicalDecodeStartupCB
 LogicalDecodeStreamAbortCB
 LogicalDecodeStreamChangeCB
 LogicalDecodeStreamCommitCB
+LogicalDecodeStreamDDLMessageCB
 LogicalDecodeStreamMessageCB
 LogicalDecodeStreamPrepareCB
 LogicalDecodeStreamStartCB
@@ -2317,6 +2320,7 @@ ReorderBufferChange
 ReorderBufferChangeType
 ReorderBufferCommitCB
 ReorderBufferCommitPreparedCB
+ReorderBufferDDLMessageCB
 ReorderBufferDiskChange
 ReorderBufferIterTXNEntry
 ReorderBufferIterTXNState
@@ -2326,6 +2330,7 @@ ReorderBufferRollbackPreparedCB
 ReorderBufferStreamAbortCB
 ReorderBufferStreamChangeCB
 ReorderBufferStreamCommitCB
+ReorderBufferStreamDDLMessageCB
 ReorderBufferStreamMessageCB
 ReorderBufferStreamPrepareCB
 ReorderBufferStreamStartCB
@@ -3919,6 +3924,7 @@ xl_heap_visible
 xl_invalid_page
 xl_invalid_page_key
 xl_invalidations
+xl_logical_ddl_message
 xl_logical_message
 xl_multi_insert_tuple
 xl_multixact_create
-- 
2.34.1

