public inbox for pgsql-general@postgresql.org  
help / color / mirror / Atom feed
From: Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com>
To: Masahiko Sawada <sawada.mshk@gmail.com>
Cc: shveta malik <shveta.malik@gmail.com>
Cc: Amit Kapila <amit.kapila16@gmail.com>
Cc: Michael Paquier <michael@paquier.xyz>
Cc: Wei Wang (Fujitsu) <wangw.fnst@fujitsu.com>
Cc: Yu Shi (Fujitsu) <shiy.fnst@fujitsu.com>
Cc: vignesh C <vignesh21@gmail.com>
Cc: Ajin Cherian <itsajin@gmail.com>
Cc: Runqi Tian <runqidev@gmail.com>
Cc: Peter Smith <smithpb2250@gmail.com>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Cc: li jie <ggysxcq@gmail.com>
Cc: Dilip Kumar <dilipbalaut@gmail.com>
Cc: Alvaro Herrera <alvherre@alvh.no-ip.org>
Cc: Japin Li <japinli@hotmail.com>
Cc: rajesh singarapu <rajesh.rs0541@gmail.com>
Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Cc: Zheng Li <zhengli10@gmail.com>
Subject: RE: Support logical replication of DDLs
Date: Tue, 18 Jul 2023 07:09:23 +0000
Message-ID: <OS0PR01MB57163E6487EFF7378CB8E17C9438A@OS0PR01MB5716.jpnprd01.prod.outlook.com> (raw)
In-Reply-To: <CAD21AoCXCAQ5QyXu9-xs30ViUHtUxQMmf-818d8GX--5pTmZ7g@mail.gmail.com>
References: <OSZPR01MB63102C42A24D59FACF6D9CD6FD4A9@OSZPR01MB6310.jpnprd01.prod.outlook.com>
	<CAJpy0uCbwqWj+p_yj1AHyiufzAUv_H29qOaztAXxFoTqZ9WcAw@mail.gmail.com>
	<OSZPR01MB6310E0509F229AF2A9414BB1FD499@OSZPR01MB6310.jpnprd01.prod.outlook.com>
	<CAJpy0uBwCZCniPR6vh26L+wpSf4xzUN8omUa9DzF-x1CAud_xA@mail.gmail.com>
	<CAA4eK1LOr+2O+_pWKTaa0y9vbW6whfm-8-fuBvnS6OBiaR+7TA@mail.gmail.com>
	<CAA4eK1LF3EaCSj5iqO0oT1k3ew7YnQbbKEgbzORDAdvdtd+r7w@mail.gmail.com>
	<CAJpy0uDtrxPo127LH3FP-TffynrspPFqhhC7o_GFOMP+2mPtWQ@mail.gmail.com>
	<OS3PR01MB6275328379FBE5734B4585619E54A@OS3PR01MB6275.jpnprd01.prod.outlook.com>
	<ZIgf3qBvFoTsMhIc@paquier.xyz>
	<CAA4eK1J8WOK9VZ9RpQRNRhRYhFOKQCu=GLCu+iBOePNO3JwLbQ@mail.gmail.com>
	<ZIjmGhPWdoJUfjjE@paquier.xyz>
	<CAJpy0uDaubBHyqPc1k0OysuBYDOVdoUgTWG4jXDCYj-OVSU8hg@mail.gmail.com>
	<CAA4eK1+K8KMsB=+jJO6wDUSt7wF1RiXKtF-HN48nCOEOv-J-3Q@mail.gmail.com>
	<CAJpy0uDLLBYAOzCePYObZ51k1epBU0hef4vbfcujKJprJVsEcQ@mail.gmail.com>
	<CAJpy0uAhLjQZ0Dh0KWDFP8mrnG0rbx99_heavwn8Ke8ZuD-Umg@mail.gmail.com>
	<OS0PR01MB5716A47D23EFAF988475D4A99431A@OS0PR01MB5716.jpnprd01.prod.outlook.com>
	<CAD21AoCXCAQ5QyXu9-xs30ViUHtUxQMmf-818d8GX--5pTmZ7g@mail.gmail.com>

On Tuesday, July 18, 2023 1:28 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> 
> On Tue, Jul 11, 2023 at 8:01 PM Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com>
> wrote:
> >
> > Hi,
> >
> > We have been researching how to create a test that detects failures
> > resulting from future syntax changes, where the deparser fails to update
> properly.
> >
> > The basic idea comes from what Robert Haas suggested in [1]: when
> > running the regression test(tests in parallel_schedule), we replace
> > the executing ddl statement with the its deparsed version and execute
> > the deparsed statement, so that we can run all the regression with the
> > deparsed statement and can expect the output to be the same as the
> > existing expected/*.out. As developers typically add new regression
> > tests to test new syntax, so we expect this test can automatically identify
> most of the new syntax changes.
> >
> > One thing to note is that when entering the event trigger(where the
> > deparsing happen), the ddl have already been executed. So we need to
> > get the deparsed statement before the execution and replace the
> > current statement with it. To achieve this, the current approach is to
> > create another database with deparser trigger and in the
> > ProcessUtility hook(e.g. tdeparser_ProcessUtility in the
> > patch) we redirect the local statement to that remote database, then
> > the statment will be deparsed and stored somewhere, we can query the
> > remote database to get the deparsed statement and use it to replace
> > the original statment.
> >
> > The process looks like:
> > 1) create another database and deparser event trigger in it before running
> the regression test.
> > 2) run the regression test (the statement will be redirect to the
> > remote database and get the deparsed statement)
> > 3) compare the output file with the expected ones.
> >
> > Attach the POC patch(0004) for this approach. Note that, the new test
> > module only test test_setup.sql, create_index.sql, create_table.sql
> > and alter_table.sql as we only support deparsing TABLE related command
> > for now. And the current test cannot pass because of some bugs in deparser,
> we will fix these bugs soon.
> >
> >
> > TO IMPROVE:
> >
> > 1. The current approach needs to handle the ERRORs, WARNINGs, and
> > NOTICEs from the remote database. Currently it will directly rethrow
> > any ERRORs encountered in the remote database. However, for WARNINGs
> > and NOTICEs, we will only rethrow them along with the ERRORs. This is
> > done to prevent duplicate messages in the output file during local
> > statement execution, which would be inconsistent with the existing
> > expected output. Note that this approach may potentially miss some
> > bugs, as there could be additional WARNINGs or NOTICEs caused by the
> deparser in the remote database.
> >
> > 2. The variable reference and assignment (xxx /gset and select
> > :var_name) will not be sent to the server(only the qualified value
> > will be sent), but it's possible the variable in another session
> > should be set to a different value, so this can cause inconsistent output.
> >
> > 3 .CREATE INDEX CONCURRENTLY will create an invalid index internally
> > even if it reports an ERROR later. But since we will directly rethrow
> > the remote ERROR in the main database, we won't execute the "CREATE
> > INDEX CONCURRENTLY" in the main database. This means that we cannot see
> the invalid index in the main database.
> >
> > To improve the above points, another variety is: run the regression test twice.
> > The first run is solely intended to collect all the deparsed
> > statements. We can dump these statements from the database and then
> > reload them in the second regression run. This allows us to utilize
> > the deparsed statements to replace the local statements in the second
> > regression run. This approach does not need to handle any remote
> > messages and client variable stuff during execution, although it could take
> more time to finsh the test.
> >
> 
> I've considered some alternative approaches but I prefer the second approach.
> A long test time could not be a big problem unless we run it by default. We can
> prepare a buildfarm animal that is configured to run the DDL deparse tests.

Thanks for the analysis.

Here is the POC patch(0004) for the second approach, In the test deparser module
folder, "Make check" will run whole regression test using the new strategy(replace
ddl statement with deparsed one). The test hasn't support meson mode yet, will
add it later. Some tests are failing because of bugs in deparser, we will fix them.

I checked the testing time of running test_setup.sql, create_index.sql,
create_table.sql and alter_table.sql for the two approaches.
The time used in DEBUG mode for the two approaches are almost the same on my
machine, the regress twice apporach takes a bit more time in RELEASE build,
which seems ok to me.

~~~debug~~~~
regress twice approach - 15s
remote database approach - 15s

~~~~release build~~~~
regress twice approach - 7.5 s
remote database approach - 6s

Best Regards,
Hou zj






Attachments:

  [application/octet-stream] 0004-test-deparser-module-regress-twice.patch (21.7K, 2-0004-test-deparser-module-regress-twice.patch)
  download | inline diff:
From ca1c8814a1026d018bdae54e64d682e32e82758e Mon Sep 17 00:00:00 2001
From: Hou Zhijie <houzj.fnst@fujitsu.com>
Date: Tue, 11 Jul 2023 19:55:55 +0800
Subject: [PATCH] test deparser module regress twice

When running the regression test(tests in parallel_schedule), we replace the
executing ddl statement with the its deparsed version and execute the
deparsed statement, so that we can run all the regression with the deparsed
statement and can expect the output to be the same as the existing
expected/*.out. As developers typically add new regression tests to test new
syntax, so we expect this test can automatically identify any new syntax
changes.

To achieve this, the strategy is to run the regression test twice. The first
run will create event triggers to deparse ddl statements, it is intended to
collect all the deparsed statements and can catch ERRORs/WARNINGs caused by
the deparser. We can dump these statements from the database and then reload
them in the second regression run. This allows us to utilize the deparsed statements
to replace the local statements in the second regression run. This approach
does not need to handle any remote messages and client variable stuff during
execution, although it could take more time to finsh the test.
---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/test_deparser/Makefile       |  57 ++++
 .../modules/test_deparser/exclusion_schedule  |   7 +
 .../test_deparser/expected/copy_cmd.out       |   3 +
 .../expected/test_deparser_1.out              |  34 ++
 .../expected/test_deparser_2.out              |  18 +
 src/test/modules/test_deparser/meson.build    |  24 ++
 .../modules/test_deparser/sql/copy_cmd.sql    |   3 +
 .../test_deparser/sql/test_deparser_1.sql     |  40 +++
 .../test_deparser/sql/test_deparser_2.sql     |  16 +
 .../test_deparser/test_deparser--1.0.sql      |   9 +
 .../modules/test_deparser/test_deparser.c     | 310 ++++++++++++++++++
 .../modules/test_deparser/test_deparser.conf  |   1 +
 .../test_deparser/test_deparser.control       |   4 +
 14 files changed, 527 insertions(+)
 create mode 100644 src/test/modules/test_deparser/Makefile
 create mode 100644 src/test/modules/test_deparser/exclusion_schedule
 create mode 100644 src/test/modules/test_deparser/expected/copy_cmd.out
 create mode 100644 src/test/modules/test_deparser/expected/test_deparser_1.out
 create mode 100644 src/test/modules/test_deparser/expected/test_deparser_2.out
 create mode 100644 src/test/modules/test_deparser/meson.build
 create mode 100644 src/test/modules/test_deparser/sql/copy_cmd.sql
 create mode 100644 src/test/modules/test_deparser/sql/test_deparser_1.sql
 create mode 100644 src/test/modules/test_deparser/sql/test_deparser_2.sql
 create mode 100644 src/test/modules/test_deparser/test_deparser--1.0.sql
 create mode 100644 src/test/modules/test_deparser/test_deparser.c
 create mode 100644 src/test/modules/test_deparser/test_deparser.conf
 create mode 100644 src/test/modules/test_deparser/test_deparser.control

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 6331c976dc..cad68a59bc 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -17,6 +17,7 @@ SUBDIRS = \
 		  test_bloomfilter \
 		  test_copy_callbacks \
 		  test_custom_rmgrs \
+		  test_deparser \
 		  test_ddl_deparse \
 		  test_extensions \
 		  test_ginpostinglist \
diff --git a/src/test/modules/test_deparser/Makefile b/src/test/modules/test_deparser/Makefile
new file mode 100644
index 0000000000..78a3ea0150
--- /dev/null
+++ b/src/test/modules/test_deparser/Makefile
@@ -0,0 +1,57 @@
+# src/test/modules/test_deparser/Makefile
+
+MODULES = test_deparser
+PGFILEDESC = "test_deparser - regression testing for DDL deparsing"
+
+EXTENSION = test_deparser
+DATA = test_deparser--1.0.sql
+
+EXTRA_CLEAN = $(pg_regress_clean_files) deparse_init_schedule deparse_regress_schedule
+
+MODULE_big = test_deparser
+OBJS = \
+	$(WIN32RES) \
+	test_deparser.o
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_deparser
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+MINIMAL_REGRESS = test_setup \
+		  create_index \
+		  create_table \
+		  alter_table
+
+REGRESS_OPTS += --load-extension=test_deparser --dlpath=$(top_builddir)/src/test/regress \
+		--temp-config $(top_srcdir)/src/test/modules/test_deparser/test_deparser.conf \
+		--inputdir=$(top_srcdir)/src/test/regress
+
+deparse_init: all deparse_init_schedule
+	$(pg_regress_check) $(REGRESS_OPTS) --schedule=deparse_init_schedule
+
+deparse_regress: all deparse_regress_schedule
+	$(pg_regress_check) $(REGRESS_OPTS) --schedule=deparse_regress_schedule
+
+check: all deparse_init deparse_regress
+
+checkminimal: all
+	$(pg_regress_check) $(REGRESS_OPTS) test_deparser_1 $(MINIMAL_REGRESS) copy_cmd
+	$(pg_regress_check) $(REGRESS_OPTS) test_deparser_2 $(MINIMAL_REGRESS)
+
+deparse_init_schedule:
+	echo "test: test_deparser_1" > $@
+	cat $(top_srcdir)/src/test/regress/parallel_schedule >> $@
+	echo "test: copy_cmd" >> $@
+	sed 's/\(.*\)/s\/\\b\1\\b\/\/g/' exclusion_schedule | sed -f - -i $@
+
+deparse_regress_schedule:
+	echo "test: test_deparser_2" > $@
+	cat $(top_srcdir)/src/test/regress/parallel_schedule >> $@
+	sed 's/\(.*\)/s\/\\b\1\\b\/\/g/' exclusion_schedule | sed -f - -i $@
diff --git a/src/test/modules/test_deparser/exclusion_schedule b/src/test/modules/test_deparser/exclusion_schedule
new file mode 100644
index 0000000000..243bb66d44
--- /dev/null
+++ b/src/test/modules/test_deparser/exclusion_schedule
@@ -0,0 +1,7 @@
+
+# For tests that don't include any DDL statement or can
+# give unexpected output because of the extra event trigger functions and table
+# created in this test, we skipping running these tests.
+opr_sanity
+misc_sanity
+event_trigger
diff --git a/src/test/modules/test_deparser/expected/copy_cmd.out b/src/test/modules/test_deparser/expected/copy_cmd.out
new file mode 100644
index 0000000000..cefc60ba4b
--- /dev/null
+++ b/src/test/modules/test_deparser/expected/copy_cmd.out
@@ -0,0 +1,3 @@
+\getenv abs_builddir PG_ABS_BUILDDIR
+\set filename :abs_builddir '/results/deparse_test_commands.data'
+COPY deparse_test_commands TO :'filename';
diff --git a/src/test/modules/test_deparser/expected/test_deparser_1.out b/src/test/modules/test_deparser/expected/test_deparser_1.out
new file mode 100644
index 0000000000..158ff55366
--- /dev/null
+++ b/src/test/modules/test_deparser/expected/test_deparser_1.out
@@ -0,0 +1,34 @@
+CREATE ROLE deparse_role SUPERUSER;
+CREATE OR REPLACE FUNCTION public.deparse_test_ddl_command_end()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+BEGIN
+	BEGIN
+		INSERT INTO pg_catalog.deparse_test_commands
+		            (id, test_name, deparse_time, original_command, command)
+		SELECT public.tdparser_get_cmdcount(),
+		current_setting('application_name'), clock_timestamp(), current_query(),
+		pg_catalog.ddl_deparse_expand_command(pg_catalog.ddl_deparse_to_json(command))
+		FROM pg_event_trigger_ddl_commands() WITH ORDINALITY;
+
+	EXCEPTION WHEN OTHERS THEN
+			RAISE EXCEPTION 'state: % errm: %', sqlstate, sqlerrm;
+	END;
+END;
+$fn$;
+SET allow_system_table_mods = on;
+CREATE UNLOGGED TABLE pg_catalog.deparse_test_commands (
+  id int,
+  test_name text,
+  deparse_time timestamptz,
+  original_command TEXT NOT NULL,
+  command TEXT
+);
+CREATE EVENT TRIGGER deparse_test_trg_sql_drop
+  ON sql_drop
+  EXECUTE PROCEDURE test_deparser_drop_command();
+CREATE EVENT TRIGGER deparse_test_trg_ddl_command_end
+  ON ddl_command_end WHEN TAG IN ('CREATE TABLE', 'ALTER TABLE')
+  EXECUTE PROCEDURE deparse_test_ddl_command_end();
diff --git a/src/test/modules/test_deparser/expected/test_deparser_2.out b/src/test/modules/test_deparser/expected/test_deparser_2.out
new file mode 100644
index 0000000000..5f069337c5
--- /dev/null
+++ b/src/test/modules/test_deparser/expected/test_deparser_2.out
@@ -0,0 +1,18 @@
+\getenv abs_builddir PG_ABS_BUILDDIR
+\set filename :abs_builddir '/results/deparse_test_commands.data'
+SET allow_system_table_mods = on;
+CREATE TABLE pg_catalog.deparse_test_commands (
+  id int,
+  test_name text,
+  deparse_time timestamptz,
+  original_command TEXT,
+  command TEXT
+);
+COPY pg_catalog.deparse_test_commands FROM :'filename';
+ALTER SYSTEM SET test_deparser.deparse_mode = on;
+SELECT pg_reload_conf();
+ pg_reload_conf 
+----------------
+ t
+(1 row)
+
diff --git a/src/test/modules/test_deparser/meson.build b/src/test/modules/test_deparser/meson.build
new file mode 100644
index 0000000000..b57553fa5a
--- /dev/null
+++ b/src/test/modules/test_deparser/meson.build
@@ -0,0 +1,24 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+test_deparser_sources = files(
+  'test_deparser.c',
+)
+
+if host_system == 'windows'
+  test_deparser_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_deparser',
+    '--FILEDESC', 'test_deparser - allow delay between parsing and execution',])
+endif
+
+test_deparser = shared_module('test_deparser',
+  test_deparser_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_deparser
+
+test_install_data += files(
+  'test_deparser.control',
+  'test_deparser--1.0.sql',
+)
+
+# TODO regression tests
diff --git a/src/test/modules/test_deparser/sql/copy_cmd.sql b/src/test/modules/test_deparser/sql/copy_cmd.sql
new file mode 100644
index 0000000000..cefc60ba4b
--- /dev/null
+++ b/src/test/modules/test_deparser/sql/copy_cmd.sql
@@ -0,0 +1,3 @@
+\getenv abs_builddir PG_ABS_BUILDDIR
+\set filename :abs_builddir '/results/deparse_test_commands.data'
+COPY deparse_test_commands TO :'filename';
diff --git a/src/test/modules/test_deparser/sql/test_deparser_1.sql b/src/test/modules/test_deparser/sql/test_deparser_1.sql
new file mode 100644
index 0000000000..1d289b5f92
--- /dev/null
+++ b/src/test/modules/test_deparser/sql/test_deparser_1.sql
@@ -0,0 +1,40 @@
+
+CREATE ROLE deparse_role SUPERUSER;
+
+CREATE OR REPLACE FUNCTION public.deparse_test_ddl_command_end()
+  RETURNS event_trigger
+  SECURITY DEFINER
+  LANGUAGE plpgsql
+AS $fn$
+BEGIN
+	BEGIN
+		INSERT INTO pg_catalog.deparse_test_commands
+		            (id, test_name, deparse_time, original_command, command)
+		SELECT public.tdparser_get_cmdcount(),
+		current_setting('application_name'), clock_timestamp(), current_query(),
+		pg_catalog.ddl_deparse_expand_command(pg_catalog.ddl_deparse_to_json(command))
+		FROM pg_event_trigger_ddl_commands() WITH ORDINALITY;
+
+	EXCEPTION WHEN OTHERS THEN
+			RAISE EXCEPTION 'state: % errm: %', sqlstate, sqlerrm;
+	END;
+END;
+$fn$;
+
+SET allow_system_table_mods = on;
+
+CREATE UNLOGGED TABLE pg_catalog.deparse_test_commands (
+  id int,
+  test_name text,
+  deparse_time timestamptz,
+  original_command TEXT NOT NULL,
+  command TEXT
+);
+
+CREATE EVENT TRIGGER deparse_test_trg_sql_drop
+  ON sql_drop
+  EXECUTE PROCEDURE test_deparser_drop_command();
+
+CREATE EVENT TRIGGER deparse_test_trg_ddl_command_end
+  ON ddl_command_end WHEN TAG IN ('CREATE TABLE', 'ALTER TABLE')
+  EXECUTE PROCEDURE deparse_test_ddl_command_end();
diff --git a/src/test/modules/test_deparser/sql/test_deparser_2.sql b/src/test/modules/test_deparser/sql/test_deparser_2.sql
new file mode 100644
index 0000000000..3bc3400b4f
--- /dev/null
+++ b/src/test/modules/test_deparser/sql/test_deparser_2.sql
@@ -0,0 +1,16 @@
+
+\getenv abs_builddir PG_ABS_BUILDDIR
+\set filename :abs_builddir '/results/deparse_test_commands.data'
+
+SET allow_system_table_mods = on;
+CREATE TABLE pg_catalog.deparse_test_commands (
+  id int,
+  test_name text,
+  deparse_time timestamptz,
+  original_command TEXT,
+  command TEXT
+);
+
+COPY pg_catalog.deparse_test_commands FROM :'filename';
+ALTER SYSTEM SET test_deparser.deparse_mode = on;
+SELECT pg_reload_conf();
diff --git a/src/test/modules/test_deparser/test_deparser--1.0.sql b/src/test/modules/test_deparser/test_deparser--1.0.sql
new file mode 100644
index 0000000000..b42f2559e6
--- /dev/null
+++ b/src/test/modules/test_deparser/test_deparser--1.0.sql
@@ -0,0 +1,9 @@
+\echo Use "CREATE EXTENSION test_deparser" to load this file. \quit
+
+CREATE FUNCTION tdparser_get_cmdcount()
+  RETURNS int STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_deparser_drop_command()
+RETURNS event_trigger STRICT SECURITY DEFINER
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_deparser/test_deparser.c b/src/test/modules/test_deparser/test_deparser.c
new file mode 100644
index 0000000000..ee55e4e4d5
--- /dev/null
+++ b/src/test/modules/test_deparser/test_deparser.c
@@ -0,0 +1,310 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_deparser.c
+ *		Test DDL deparser
+ *
+ * Copyright (c) 2020-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/test/modules/test_deparser/test_deparser.c
+ *
+ * When running the regression test(tests in parallel_schedule), we replace the
+ * executing ddl statement with the its deparsed version and execute the
+ * deparsed statement, so that we can run all the regression with the deparsed
+ * statement and can expect the output to be the same as the existing
+ * expected *.out. As developers typically add new regression tests to test new
+ * syntax, so we expect this test can automatically identify any new syntax
+ * changes.
+ *
+ * The strategy is to run the regression test twice. The first run will create
+ * event triggers to deparse ddl statements, it is intended to collect all the
+ * deparsed statements and can catch ERRORs/WARNINGs caused by the deparser. We
+ * can dump these statements from the database and then reload them in the
+ * second regression run. This allows us to utilize the deparsed statements to
+ * replace the local statements in the second regression run. This approach
+ * does not need to handle any remote messages and client variable stuff during
+ * execution, although it could take more time to finsh the test.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/dependency.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "commands/seclabel.h"
+#include "executor/executor.h"
+#include "executor/spi.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "tcop/ddldeparse.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/portal.h"
+#include "utils/queryenvironment.h"
+
+PG_MODULE_MAGIC;
+
+static ProcessUtility_hook_type prev_ProcessUtility = NULL;
+static ExecutorRun_hook_type prev_ExecutorRun = NULL;
+extern EventTriggerQueryState *currentEventTriggerState;
+
+static void tdeparser_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
+									 bool readOnlyTree,
+									 ProcessUtilityContext context, ParamListInfo params,
+									 QueryEnvironment *queryEnv,
+									 DestReceiver *dest, QueryCompletion *qc);
+
+static void tdeparser_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count,
+		bool execute_once);
+
+static int	nesting_level = 0;
+static int	cmd_count = 0;
+static bool	deparse_mode = false;
+
+
+static List *
+change_deparsed_stmt(PlannedStmt *pstmt, const char *queryString)
+{
+	List		   *nstmt_list = NIL;
+	List		   *parsetree_list;
+	ListCell	   *lc;
+	Oid				save_userid = 0;
+	const char	   *deparsed_cmd = queryString;
+	int				save_sec_context = 0;
+	int				i;
+	SPITupleTable  *tuptable;
+	StringInfoData	cmd;
+
+	if (!deparse_mode)
+		return list_make1(pstmt);
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "SELECT command, id, deparse_time from pg_catalog.deparse_test_commands "
+				"WHERE id = %d AND test_name = '%s' AND original_command = current_query() order by deparse_time",
+				cmd_count, application_name);
+	elog(LOG, "ID: %d", cmd_count);
+
+	/* Change to superuser to avoid access deny. */
+	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+	SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
+			save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+	SPI_connect();
+
+	/* Query the deparsed statement. */
+	if (SPI_exec(cmd.data, 1024) != SPI_OK_SELECT)
+		elog(ERROR, "SPI_exec failed: %s", cmd.data);
+
+	if (SPI_tuptable == NULL)
+		elog(ERROR, "could not find deparsed statement");
+
+	tuptable = SPI_tuptable;
+
+	/*
+	 * Return if the statement was not deparsed which means this statement is
+	 * expected to fail.
+	 */
+	if (tuptable->numvals == 0)
+	{
+		SetUserIdAndSecContext(save_userid, save_sec_context);
+		elog(LOG, "no deparsed statement");
+		SPI_finish();
+		return list_make1(pstmt);
+	}
+
+	resetStringInfo(&cmd);
+
+	for (i = 0 ; i < tuptable->numvals; i++)
+	{
+		char *value  = SPI_getvalue(tuptable->vals[i], tuptable->tupdesc, 1);
+		appendStringInfo(&cmd, "%s;", value ? value : " ");
+	}
+
+	elog(LOG, "EXECQ: %s", cmd.data);
+
+	deparsed_cmd = cmd.data;
+
+	SPI_finish();
+
+	SetUserIdAndSecContext(save_userid, save_sec_context);
+
+	parsetree_list = pg_parse_query(deparsed_cmd);
+
+	/*
+	 * One statment could be deparsed into mulitiple statements, for example:
+	 * "DROP TABLE t1,t2" will be deparsed into "DROP TABLE t1" AND "DROP TABLE
+	 * t2". So we need to maintain them in a list.
+	 */
+	foreach(lc, parsetree_list)
+	{
+		List	   *plans;
+		RawStmt    *rs = lfirst_node(RawStmt, lc);
+		List	   *querytree_list = pg_analyze_and_rewrite_fixedparams(rs, deparsed_cmd,
+																		NULL, 0, NULL);
+
+		Assert(list_length(querytree_list) == 1);
+
+		plans = pg_plan_queries(querytree_list, deparsed_cmd,
+								CURSOR_OPT_PARALLEL_OK, NULL);
+
+		Assert(list_length(plans) == 1);
+
+		nstmt_list = lappend(nstmt_list, linitial(plans));
+	}
+
+	return nstmt_list;
+}
+
+/*
+ * ProcessUtility hook
+ */
+static void
+tdeparser_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
+						 bool readOnlyTree,
+						 ProcessUtilityContext context,
+						 ParamListInfo params, QueryEnvironment *queryEnv,
+						 DestReceiver *dest, QueryCompletion *qc)
+{
+	List *nplan_list;
+	ListCell *lc;
+	CommandTag tag = CreateCommandTag(pstmt->utilityStmt);
+
+	nesting_level++;
+	PG_TRY();
+	{
+		if (nesting_level == 1 && command_tag_event_trigger_ok(tag))
+			nplan_list = change_deparsed_stmt(pstmt, queryString);
+		else
+			nplan_list = list_make1(pstmt);
+
+		foreach(lc, nplan_list)
+		{
+			PlannedStmt *plan = (PlannedStmt *) lfirst(lc);
+			if (prev_ProcessUtility)
+				prev_ProcessUtility(plan, queryString, readOnlyTree,
+									context, params, queryEnv,
+									dest, qc);
+			else
+				standard_ProcessUtility(plan, queryString, readOnlyTree,
+										context, params, queryEnv,
+										dest, qc);
+		}
+	}
+	PG_FINALLY();
+	{
+		if (nesting_level == 1)
+			cmd_count++;
+
+		nesting_level--;
+	}
+	PG_END_TRY();
+}
+
+/*
+ * ExecutorRun hook: all we need do is track nesting depth
+ */
+static void
+tdeparser_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count,
+			bool execute_once)
+{
+	nesting_level++;
+	PG_TRY();
+	{
+		if (prev_ExecutorRun)
+			prev_ExecutorRun(queryDesc, direction, count, execute_once);
+		else
+			standard_ExecutorRun(queryDesc, direction, count, execute_once);
+	}
+	PG_FINALLY();
+	{
+		nesting_level--;
+	}
+	PG_END_TRY();
+}
+
+PG_FUNCTION_INFO_V1(tdparser_get_cmdcount);
+PG_FUNCTION_INFO_V1(test_deparser_drop_command);
+
+Datum
+tdparser_get_cmdcount(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(cmd_count);
+}
+
+Datum
+test_deparser_drop_command(PG_FUNCTION_ARGS)
+{
+	slist_iter	iter;
+
+	/* 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)
+		{
+			char	*command;
+
+			command = deparse_drop_table(obj->objidentity, trigdata->parsetree);
+			if (command)
+			{
+				StringInfoData cmd;
+
+				command = deparse_ddl_json_to_string(command);
+				initStringInfo(&cmd);
+
+				appendStringInfo(&cmd, "INSERT INTO pg_catalog.deparse_test_commands "
+						"(id, test_name, deparse_time, original_command, command) "
+						"SELECT public.tdparser_get_cmdcount(), "
+						"current_setting('application_name'), "
+						"clock_timestamp(), current_query(), '%s'", command);
+
+				/* insert deparsed statement into table */
+				SPI_connect();
+				if (SPI_exec(cmd.data, 8) != SPI_OK_INSERT)
+					elog(ERROR, "SPI_exec failed: %s", cmd.data);
+				SPI_finish();
+			}
+		}
+	}
+
+	return PointerGetDatum(NULL);
+}
+
+/* Module load function */
+void
+_PG_init(void)
+{
+	DefineCustomBoolVariable("test_deparser.deparse_mode",
+			"Change the statement to deparsed one",
+			NULL,
+			&deparse_mode,
+			false,
+			PGC_SUSET,
+			0,
+			NULL,
+			NULL,
+			NULL);
+
+	prev_ProcessUtility = ProcessUtility_hook;
+	ProcessUtility_hook = tdeparser_ProcessUtility;
+
+	prev_ExecutorRun = ExecutorRun_hook;
+	ExecutorRun_hook = tdeparser_ExecutorRun;
+}
diff --git a/src/test/modules/test_deparser/test_deparser.conf b/src/test/modules/test_deparser/test_deparser.conf
new file mode 100644
index 0000000000..35865b5c5a
--- /dev/null
+++ b/src/test/modules/test_deparser/test_deparser.conf
@@ -0,0 +1 @@
+session_preload_libraries = 'test_deparser'
diff --git a/src/test/modules/test_deparser/test_deparser.control b/src/test/modules/test_deparser/test_deparser.control
new file mode 100644
index 0000000000..c3e9eaf064
--- /dev/null
+++ b/src/test/modules/test_deparser/test_deparser.control
@@ -0,0 +1,4 @@
+comment = 'Test code for DDL deparse feature'
+default_version = '1.0'
+module_pathname = '$libdir/test_deparser'
+relocatable = true
\ No newline at end of file
-- 
2.30.0.windows.2



  [application/octet-stream] 0001-Deparser-for-Create-And-Drop-Table-DDL-co-2023_06_30.patch (98.1K, 3-0001-Deparser-for-Create-And-Drop-Table-DDL-co-2023_06_30.patch)
  download | inline diff:
From b4ec73d42598e7218251d35da29c8aac7781a381 Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Mon, 22 May 2023 08:36:49 +0530
Subject: [PATCH 1/5] Deparser for Create And Drop Table DDL commands.

This patch constructs JSON blobs representing DDL commands, which can
later be re-processed into plain strings by well-defined sprintf-like
expansion. These JSON objects are intended to allow for machine-editing of
the commands, by replacing certain nodes within the objects.

Much of the information in the output blob actually comes from system
catalogs, not from the command parse node, as it is impossible to reliably
construct a fully-specified command (i.e. one not dependent on search_path
etc) looking only at the parse node.

This provides a base for logical replication of DDL statements. Currently,
the patch has support for CREATE and DROP TABLE
---
 src/backend/catalog/pg_inherits.c     |   36 +
 src/backend/commands/Makefile         |    2 +
 src/backend/commands/ddldeparse.c     | 1891 +++++++++++++++++++++++++
 src/backend/commands/ddljson.c        |  759 ++++++++++
 src/backend/commands/event_trigger.c  |    1 +
 src/backend/commands/meson.build      |    2 +
 src/backend/commands/sequence.c       |   43 +
 src/backend/commands/tablecmds.c      |    3 +-
 src/backend/parser/parse_utilcmd.c    |    1 +
 src/backend/partitioning/partbounds.c |   28 +
 src/backend/utils/adt/format_type.c   |  108 +-
 src/backend/utils/adt/ruleutils.c     |   52 +-
 src/include/catalog/pg_inherits.h     |    2 +
 src/include/catalog/pg_proc.dat       |    7 +
 src/include/commands/sequence.h       |    9 +
 src/include/commands/tablecmds.h      |    2 +
 src/include/nodes/parsenodes.h        |    1 +
 src/include/partitioning/partbounds.h |    2 +
 src/include/tcop/ddldeparse.h         |   21 +
 src/include/utils/builtins.h          |    5 +
 src/include/utils/ruleutils.h         |   13 +
 src/tools/pgindent/typedefs.list      |    2 +
 22 files changed, 2974 insertions(+), 16 deletions(-)
 create mode 100644 src/backend/commands/ddldeparse.c
 create mode 100644 src/backend/commands/ddljson.c
 create mode 100644 src/include/tcop/ddldeparse.h

diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index da969bd2f9..36b164575f 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -655,3 +655,39 @@ PartitionHasPendingDetach(Oid partoid)
 	elog(ERROR, "relation %u is not a partition", partoid);
 	return false;				/* keep compiler quiet */
 }
+
+/*
+ * Given a table OID, return a schema-qualified table list representing
+ * the parent tables.
+ */
+List *
+relation_get_inh_parents(Oid objectId)
+{
+	List	   *parents = NIL;
+	Relation	inhRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple;
+
+	inhRel = table_open(InheritsRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(objectId));
+
+	scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId,
+							  true, NULL, 1, &key);
+
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple);
+
+		parents = lappend_oid(parents, formInh->inhparent);
+	}
+
+	systable_endscan(scan);
+	table_close(inhRel, RowExclusiveLock);
+
+	return parents;
+}
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..076ac4eb31 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -29,6 +29,8 @@ OBJS = \
 	copyto.o \
 	createas.o \
 	dbcommands.o \
+	ddldeparse.o \
+	ddljson.o \
 	define.o \
 	discard.o \
 	dropcmds.o \
diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c
new file mode 100644
index 0000000000..c8e885ca1a
--- /dev/null
+++ b/src/backend/commands/ddldeparse.c
@@ -0,0 +1,1891 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddldeparse.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *
+ * This is intended to provide JSON blobs representing DDL commands, which can
+ * later be re-processed into plain strings by well-defined sprintf-like
+ * expansion.  These JSON objects are intended to allow for machine-editing of
+ * the commands, by replacing certain nodes within the objects.
+ *
+ * Much of the information in the output blob actually comes from system
+ * catalogs, not from the command parse node, as it is impossible to reliably
+ * construct a fully-specified command (i.e. one not dependent on search_path
+ * etc) looking only at the parse node.
+ *
+ * Deparsed JsonbValue is created by using:
+ * 	new_jsonb_VA where the key-value pairs composing an jsonb object can be
+ * 	derived using the passed variable arguments. In order to successfully
+ *  construct one key:value pair, a set of three arguments consisting of a name
+ * 	(string), type (from the jbvType enum) and value must be supplied. It can
+ *  take multiple such sets and construct multiple key-value pairs and append
+ *  those to output parse-state.
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/ddldeparse.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/relation.h"
+#include "access/table.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "commands/tablespace.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "partitioning/partbounds.h"
+#include "tcop/ddldeparse.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Estimated length of the generated jsonb string */
+#define JSONB_ESTIMATED_LEN 128
+
+/*
+ * Return the string representation of the given RELPERSISTENCE value.
+ */
+static char *
+get_persistence_str(char persistence)
+{
+	switch (persistence)
+	{
+		case RELPERSISTENCE_TEMP:
+			return "TEMPORARY";
+		case RELPERSISTENCE_UNLOGGED:
+			return "UNLOGGED";
+		case RELPERSISTENCE_PERMANENT:
+			return NULL;
+		default:
+			elog(ERROR, "unexpected persistence marking %c",
+				 persistence);
+			return NULL;		/* make compiler happy */
+	}
+}
+
+/*
+ * Insert JsonbValue key to the output parse state.
+ */
+static void
+insert_jsonb_key(JsonbParseState *state, char *name)
+{
+	JsonbValue	key;
+
+	/* Push the key */
+	key.type = jbvString;
+	key.val.string.val = name;
+	key.val.string.len = strlen(name);
+	pushJsonbValue(&state, WJB_KEY, &key);
+}
+
+/*
+ * Append new jsonb key:value pairs to the output parse state -- varargs
+ * function
+ *
+ * Arguments:
+ * "state": the output jsonb state where each key-value pair is pushed.
+ *
+ * "numobjs": the number of key:value pairs to be pushed to JsonbParseState;
+ * for each one, a name (string), type (from the jbvType enum) and value must
+ * be supplied.  The value must match the type given; for instance, jbvBool
+ * requires an bool, jbvString requires a char * and so on.
+ * Each element type must match the conversion specifier given in the format
+ * string, as described in ddl_deparse_expand_command.
+ *
+ * Notes:
+ * a) The caller can pass "fmt":"fmtstr" as a regular key:value pair to this,
+ * no special handling needed for that.
+ * b) The caller need to carefully pass sets of arguments, we don't have the
+ * luxury of sprintf-like compiler warnings for malformed argument lists.
+ */
+static void
+new_jsonb_VA(JsonbParseState *state, int numobjs,...)
+{
+	va_list		args;
+	int			i;
+	JsonbValue	val;
+
+	/* Process the given varargs */
+	va_start(args, numobjs);
+
+	for (i = 0; i < numobjs; i++)
+	{
+		char	   *name;
+		enum jbvType type;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, enum jbvType);
+
+		/* Push the key first */
+		insert_jsonb_key(state, name);
+
+		/*
+		 * For all param types other than jbvNull, there must be a value in
+		 * the varargs. Fetch it and add the fully formed subobject into the
+		 * main object.
+		 */
+		switch (type)
+		{
+			case jbvNull:
+				/* Null params don't have a value (obviously) */
+				val.type = jbvNull;
+				pushJsonbValue(&state, WJB_VALUE, &val);
+				break;
+
+			case jbvBool:
+				/* Push the bool value */
+				val.type = jbvBool;
+				val.val.boolean = va_arg(args, int);
+				pushJsonbValue(&state, WJB_VALUE, &val);
+				break;
+
+			case jbvString:
+				/* Push the string value */
+				val.type = jbvString;
+				val.val.string.val = pstrdup(va_arg(args, char *));
+				val.val.string.len = strlen(val.val.string.val);
+				pushJsonbValue(&state, WJB_VALUE, &val);
+				break;
+
+			case jbvNumeric:
+				/* Push the numeric value */
+				val.type = jbvNumeric;
+				val.val.numeric = (Numeric)
+					DatumGetNumeric(DirectFunctionCall1(
+														int8_numeric,
+														va_arg(args, int)));
+
+				pushJsonbValue(&state, WJB_VALUE, &val);
+				break;
+
+			default:
+				elog(ERROR, "unrecognized jbvType %d", type);
+		}
+	}
+
+	va_end(args);
+}
+
+/*
+ * A helper routine to insert jsonb for typId to the output parse state.
+ */
+static void
+new_jsonb_for_type(JsonbParseState *state, char *parentKey,
+				   Oid typId, int32 typmod)
+{
+	Oid			typnspid;
+	char	   *type_nsp;
+	char	   *type_name = NULL;
+	char	   *typmodstr;
+	bool		type_array;
+
+	Assert(parentKey);
+
+	format_type_detailed(typId, typmod, &typnspid, &type_name, &typmodstr,
+						 &type_array);
+
+	if (OidIsValid(typnspid))
+		type_nsp = get_namespace_name_or_temp(typnspid);
+	else
+		type_nsp = pstrdup("");
+
+	insert_jsonb_key(state, parentKey);
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	new_jsonb_VA(state, 4,
+				 "schemaname", jbvString, type_nsp,
+				 "typename", jbvString, type_name,
+				 "typmod", jbvString, typmodstr,
+				 "typarray", jbvBool, type_array);
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * A helper routine to set up name: schemaname, objname
+ *
+ * Elements "schema_name" and "obj_name" are set.  If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the obj_name will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static void
+new_jsonb_for_qualname(JsonbParseState *state, Oid nspid, char *objName,
+					   char *keyName, bool createObject)
+{
+	char	   *namespace;
+
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+
+	/* Push the key first */
+	if (keyName)
+		insert_jsonb_key(state, keyName);
+
+	if (createObject)
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	new_jsonb_VA(state, 2,
+				 "schemaname", jbvString, namespace,
+				 "objname", jbvString, objName);
+
+	if (createObject)
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * A helper routine to set up name: 'schemaname, objname' where the object is
+ * specified by classId and objId.
+ */
+static void
+new_jsonb_for_qualname_id(JsonbParseState *state, Oid classId, Oid objectId,
+						  char *keyName, bool createObject)
+{
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		obj_nsp;
+	Datum		obj_name;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	AttrNumber	Anum_oid = get_object_attnum_oid(classId);
+	bool		isnull;
+
+	catalog = table_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, Anum_oid, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object with OID %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	obj_nsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "null namespace for object %u", objectId);
+
+	obj_name = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+							&isnull);
+	if (isnull)
+		elog(ERROR, "null attribute name for object %u", objectId);
+
+	new_jsonb_for_qualname(state, DatumGetObjectId(obj_nsp),
+						   NameStr(*DatumGetName(obj_name)),
+						   keyName, createObject);
+	table_close(catalog, AccessShareLock);
+}
+
+/*
+ * A helper routine to insert key:value where value is array of qualname to
+ * the output parse state.
+ */
+static void
+new_jsonbArray_for_qualname_id(JsonbParseState *state,
+							   char *keyname, List *array)
+{
+	ListCell   *lc;
+
+	/* Push the key first */
+	insert_jsonb_key(state, keyname);
+
+	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+	/* Push the array elements now */
+	foreach(lc, array)
+		new_jsonb_for_qualname_id(state, RelationRelationId, lfirst_oid(lc),
+								  NULL, true);
+
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+}
+
+/*
+ * A helper routine to insert collate object for column
+ * definition to the output parse state.
+ */
+static void
+insert_collate_object(JsonbParseState *state, char *parentKey, char *fmt,
+					  Oid classId, Oid objectId, char *key)
+{
+	/*
+	 * Insert parent key for which we are going to create value object here.
+	 */
+	if (parentKey)
+		insert_jsonb_key(state, parentKey);
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	new_jsonb_VA(state, 1, "fmt", jbvString, fmt);
+
+	/* push object now */
+	new_jsonb_for_qualname_id(state, classId, objectId, key, true);
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * A helper routine to insert identity object for the table definition
+ * to the output parse state.
+ */
+static void
+insert_identity_object(JsonbParseState *state, Oid nspid, char *relname)
+{
+	new_jsonb_for_qualname(state, nspid, relname, "identity", true);
+}
+
+/*
+ * Deparse the sequence CACHE option to Jsonb
+ *
+ * Verbose syntax
+ * CACHE %{value}
+ */
+static inline void
+deparse_Seq_Cache(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	new_jsonb_VA(state, 3,
+				 "fmt", jbvString, "CACHE %{value}s",
+				 "clause", jbvString, "cache",
+				 "value", jbvString,
+				 psprintf(INT64_FORMAT, seqdata->seqcache));
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence CYCLE option to Jsonb.
+ *
+ * Verbose syntax
+ * %{no}s CYCLE
+ */
+static inline void
+deparse_Seq_Cycle(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+	StringInfoData fmtStr;
+
+	initStringInfo(&fmtStr);
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	if (!seqdata->seqcycle)
+	{
+		appendStringInfoString(&fmtStr, "%{no}s ");
+		new_jsonb_VA(state, 1, "no", jbvString, "NO");
+	}
+
+	appendStringInfoString(&fmtStr, "CYCLE");
+	new_jsonb_VA(state, 2,
+				 "fmt", jbvString, fmtStr.data,
+				 "clause", jbvString, "cycle");
+
+	pfree(fmtStr.data);
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence INCREMENT BY option to Jsonb
+ *
+ * Verbose syntax
+ * INCREMENT BY %{value}s
+ */
+static inline void
+deparse_Seq_IncrementBy(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	new_jsonb_VA(state, 3,
+				 "fmt", jbvString, "INCREMENT BY %{value}s",
+				 "clause", jbvString, "seqincrement",
+				 "value", jbvString,
+				 psprintf(INT64_FORMAT, seqdata->seqincrement));
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence MAXVALUE option to Jsonb.
+ *
+ * Verbose syntax
+ * MAXVALUE %{value}s
+ */
+static inline void
+deparse_Seq_Maxvalue(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	new_jsonb_VA(state, 3,
+				 "fmt", jbvString, "MAXVALUE %{value}s",
+				 "clause", jbvString, "maxvalue",
+				 "value", jbvString,
+				 psprintf(INT64_FORMAT, seqdata->seqmax));
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence MINVALUE option to Jsonb
+ *
+ * Verbose syntax
+ * MINVALUE %{value}s
+ */
+static inline void
+deparse_Seq_Minvalue(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	new_jsonb_VA(state, 3,
+				 "fmt", jbvString, "MINVALUE %{value}s",
+				 "clause", jbvString, "minvalue",
+				 "value", jbvString,
+				 psprintf(INT64_FORMAT, seqdata->seqmin));
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence OWNED BY command.
+ *
+ * Verbose syntax
+ * OWNED BY %{owner}D
+ */
+static void
+deparse_Seq_OwnedBy(JsonbParseState *state, Oid sequenceId)
+{
+	Relation	depRel;
+	SysScanDesc scan;
+	ScanKeyData keys[3];
+	HeapTuple	tuple;
+	bool		elem_found PG_USED_FOR_ASSERTS_ONLY = false;
+
+	depRel = table_open(DependRelationId, AccessShareLock);
+	ScanKeyInit(&keys[0],
+				Anum_pg_depend_classid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationRelationId));
+	ScanKeyInit(&keys[1],
+				Anum_pg_depend_objid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(sequenceId));
+	ScanKeyInit(&keys[2],
+				Anum_pg_depend_objsubid,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(0));
+
+	scan = systable_beginscan(depRel, DependDependerIndexId, true,
+							  NULL, 3, keys);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Oid			ownerId;
+		Form_pg_depend depform;
+		char	   *colname;
+
+		depform = (Form_pg_depend) GETSTRUCT(tuple);
+
+		/* Only consider AUTO dependencies on pg_class */
+		if (depform->deptype != DEPENDENCY_AUTO)
+			continue;
+		if (depform->refclassid != RelationRelationId)
+			continue;
+		if (depform->refobjsubid <= 0)
+			continue;
+
+		ownerId = depform->refobjid;
+		colname = get_attname(ownerId, depform->refobjsubid, false);
+
+		/* mark the begin of owner's definition object */
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+		new_jsonb_VA(state, 2,
+					 "fmt", jbvString, "OWNED BY %{owner}D",
+					 "clause", jbvString, "owned");
+
+		/* owner key */
+		insert_jsonb_key(state, "owner");
+
+		/* owner value begin */
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+		new_jsonb_for_qualname_id(state, RelationRelationId,
+								  ownerId, NULL, false);
+		new_jsonb_VA(state, 1, "attrname", jbvString, colname);
+
+		/* owner value end */
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+		/* mark the end of owner's definition object */
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+#ifdef USE_ASSERT_CHECKING
+		elem_found = true;
+#endif
+	}
+
+	systable_endscan(scan);
+	relation_close(depRel, AccessShareLock);
+
+	/*
+	 * If there's no owner column, assert. The caller must have checked
+	 * presence of owned_by element before invoking this.
+	 */
+	Assert(elem_found);
+}
+
+/*
+ * Deparse the sequence START WITH option to Jsonb.
+ *
+ * Verbose syntax
+ * START WITH %{value}s
+ */
+static inline void
+deparse_Seq_Startwith(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	new_jsonb_VA(state, 3,
+				 "fmt", jbvString, "START WITH %{value}s",
+				 "clause", jbvString, "start",
+				 "value", jbvString,
+				 psprintf(INT64_FORMAT, seqdata->seqstart));
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence RESTART option to Jsonb
+ *
+ * Verbose syntax
+ * RESTART %{value}s
+ */
+static inline void
+deparse_Seq_Restart(JsonbParseState *state, int64 last_value)
+{
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	new_jsonb_VA(state, 3,
+				 "fmt", jbvString, "RESTART %{value}s",
+				 "clause", jbvString, "restart",
+				 "value", jbvString,
+				 psprintf(INT64_FORMAT, last_value));
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence AS option.
+ *
+ * Verbose syntax
+ * AS %{seqtype}T
+ */
+static inline void
+deparse_Seq_As(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	new_jsonb_VA(state, 1, "fmt", jbvString, "AS %{seqtype}T");
+	new_jsonb_for_type(state, "seqtype", seqdata->seqtypid, -1);
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the definition of column identity to Jsonb.
+ *
+ * Verbose syntax
+ * %{identity_type}s ( %{seq_definition: }s )
+ * where identity_type is: "GENERATED %{option}s AS IDENTITY"
+ */
+static void
+deparse_ColumnIdentity(JsonbParseState *state, char *parentKey,
+					   Oid seqrelid, char identity)
+{
+	Form_pg_sequence seqform;
+	Sequence_values *seqvalues;
+	StringInfoData fmtStr;
+
+	initStringInfo(&fmtStr);
+
+	/*
+	 * Insert parent key for which we are going to create value object here.
+	 */
+	if (parentKey)
+		insert_jsonb_key(state, parentKey);
+
+	/* create object now for value of identity_column */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	/* identity_type object creation */
+	if (identity == ATTRIBUTE_IDENTITY_ALWAYS ||
+		identity == ATTRIBUTE_IDENTITY_BY_DEFAULT)
+	{
+		appendStringInfoString(&fmtStr, "%{identity_type}s");
+		insert_jsonb_key(state, "identity_type");
+
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+		new_jsonb_VA(state, 2,
+					 "fmt", jbvString,
+					 "GENERATED %{option}s AS IDENTITY",
+					 "option", jbvString,
+					 (identity == ATTRIBUTE_IDENTITY_ALWAYS ?
+					  "ALWAYS" : "BY DEFAULT"));
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+	}
+
+	/* seq_definition array object creation */
+	insert_jsonb_key(state, "seq_definition");
+
+	appendStringInfoString(&fmtStr, " ( %{seq_definition: }s )");
+
+	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+	seqvalues = get_sequence_values(seqrelid);
+	seqform = seqvalues->seqform;
+
+	/* Definition elements */
+	deparse_Seq_Cache(state, seqform);
+	deparse_Seq_Cycle(state, seqform);
+	deparse_Seq_IncrementBy(state, seqform);
+	deparse_Seq_Minvalue(state, seqform);
+	deparse_Seq_Maxvalue(state, seqform);
+	deparse_Seq_Startwith(state, seqform);
+	deparse_Seq_Restart(state, seqvalues->last_value);
+	/* We purposefully do not emit OWNED BY here */
+
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+	/* We have full fmt by now, so add jsonb element for that */
+	new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+	pfree(fmtStr.data);
+
+	/* end of idendity_column object */
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse a ColumnDef node within a regular (non-typed) table creation.
+ *
+ * NOT NULL constraints in the column definition are emitted directly in the
+ * column definition by this routine; other constraints must be emitted
+ * elsewhere (the info in the parse node is incomplete anyway).
+ *
+ * Verbose syntax
+ * "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s
+ *  %{not_null}s %{default}s %{identity_column}s %{generated_column}s"
+ */
+static void
+deparse_ColumnDef(JsonbParseState *state, Relation relation,
+				  List *dpcontext, bool composite, ColumnDef *coldef)
+{
+	Oid			relid = RelationGetRelid(relation);
+	HeapTuple	attrTup;
+	Form_pg_attribute attrForm;
+	Oid			typid;
+	int32		typmod;
+	Oid			typcollation;
+	bool		saw_notnull;
+	ListCell   *cell;
+	StringInfoData fmtStr;
+
+	initStringInfo(&fmtStr);
+
+	/*
+	 * Inherited columns without local definitions should be skipped. We don't
+	 * want those to be part of final string.
+	 */
+	if (!coldef->is_local)
+		return;
+
+	attrTup = SearchSysCacheAttName(relid, coldef->colname);
+	if (!HeapTupleIsValid(attrTup))
+		elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
+			 coldef->colname, relid);
+	attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
+
+	get_atttypetypmodcoll(relid, attrForm->attnum,
+						  &typid, &typmod, &typcollation);
+
+	/* start making column object */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	/* create name and type elements for column */
+	appendStringInfoString(&fmtStr, "%{name}I");
+	new_jsonb_VA(state, 2,
+				 "name", jbvString, coldef->colname,
+				 "type", jbvString, "column");
+
+	/*
+	 * create coltype object having 4 elements: schemaname, typename, typemod,
+	 * typearray
+	 */
+	appendStringInfoString(&fmtStr, " %{coltype}T");
+	new_jsonb_for_type(state, "coltype", typid, typmod);
+
+	/* STORAGE clause */
+	if (!composite)
+	{
+		appendStringInfoString(&fmtStr, " STORAGE %{colstorage}s");
+		new_jsonb_VA(state, 1,
+					 "colstorage", jbvString,
+					 storage_name(attrForm->attstorage));
+	}
+
+	/* COMPRESSION clause */
+	if (coldef->compression)
+	{
+		appendStringInfoString(&fmtStr, " %{compression}s");
+		insert_jsonb_key(state, "compression");
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+		new_jsonb_VA(state, 2,
+					 "fmt", jbvString, "COMPRESSION %{compression_method}I",
+					 "compression_method", jbvString, coldef->compression);
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+	}
+
+	/* COLLATE clause */
+	if (OidIsValid(typcollation))
+	{
+		appendStringInfoString(&fmtStr, " %{collation}s");
+		insert_collate_object(state, "collation",
+							  "COLLATE %{collation_name}D",
+							  CollationRelationId, typcollation,
+							  "collation_name");
+	}
+
+	if (!composite)
+	{
+		Oid			seqrelid = InvalidOid;
+
+		/*
+		 * Emit a NOT NULL declaration if necessary.  Note that we cannot
+		 * trust pg_attribute.attnotnull here, because that bit is also set
+		 * when primary keys are specified; we must not emit a NOT NULL
+		 * constraint in that case, unless explicitly specified.  Therefore,
+		 * we scan the list of constraints attached to this column to
+		 * determine whether we need to emit anything. (Fortunately, NOT NULL
+		 * constraints cannot be table constraints.)
+		 *
+		 * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is
+		 * marked is_not_null.
+		 */
+		saw_notnull = false;
+		foreach(cell, coldef->constraints)
+		{
+			Constraint *constr = (Constraint *) lfirst(cell);
+
+			if (constr->contype == CONSTR_NOTNULL)
+			{
+				saw_notnull = true;
+				break;
+			}
+		}
+
+		/* NOT NULL */
+		if (saw_notnull)
+		{
+			appendStringInfoString(&fmtStr, " %{not_null}s");
+			new_jsonb_VA(state, 1,
+						 "not_null", jbvString, "NOT NULL");
+		}
+
+
+		/* DEFAULT */
+		if (attrForm->atthasdef &&
+			coldef->generated != ATTRIBUTE_GENERATED_STORED)
+		{
+			char	   *defstr;
+
+			appendStringInfoString(&fmtStr, " %{default}s");
+
+			defstr = relation_get_column_default(relation, attrForm->attnum,
+												 dpcontext);
+
+			insert_jsonb_key(state, "default");
+			pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+			new_jsonb_VA(state, 2,
+						 "fmt", jbvString, "DEFAULT %{default}s",
+						 "default", jbvString, defstr);
+
+			pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+		}
+
+		/* IDENTITY COLUMN */
+		if (coldef->identity)
+		{
+			/*
+			 * For identity column, find the sequence owned by column in order
+			 * to deparse the column definition.
+			 */
+			seqrelid = getIdentitySequence(relid, attrForm->attnum, true);
+			if (OidIsValid(seqrelid) && coldef->identitySequence)
+				seqrelid = RangeVarGetRelid(coldef->identitySequence,
+											NoLock, false);
+
+			if (OidIsValid(seqrelid))
+			{
+				appendStringInfoString(&fmtStr, " %{identity_column}s");
+				deparse_ColumnIdentity(state, "identity_column",
+									   seqrelid,
+									   coldef->identity);
+			}
+		}
+
+
+		/* GENERATED COLUMN EXPRESSION */
+		if (coldef->generated == ATTRIBUTE_GENERATED_STORED)
+		{
+			char	   *defstr;
+
+			appendStringInfoString(&fmtStr, " %{generated_column}s");
+			defstr = relation_get_column_default(relation, attrForm->attnum,
+												 dpcontext);
+
+			insert_jsonb_key(state, "generated_column");
+			pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+			new_jsonb_VA(state, 2,
+						 "fmt", jbvString, "GENERATED ALWAYS AS"
+						 " (%{generation_expr}s) STORED",
+						 "generation_expr", jbvString, defstr);
+
+			pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+		}
+	}
+
+	ReleaseSysCache(attrTup);
+
+	/* We have full fmt by now, so add jsonb element for that */
+	new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+	pfree(fmtStr.data);
+
+	/* mark the end of one column object */
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Helper for deparse_ColumnDef_typed()
+ *
+ * Returns true if we need to deparse a ColumnDef node within a typed
+ * table creation.
+ */
+static bool
+deparse_ColDef_typed_needed(Relation relation, ColumnDef *coldef,
+							Form_pg_attribute *atFormOut, bool *notnull)
+{
+	Oid			relid = RelationGetRelid(relation);
+	HeapTuple	attrTup;
+	Form_pg_attribute attrForm;
+	Oid			typid;
+	int32		typmod;
+	Oid			typcollation;
+	bool		saw_notnull;
+	ListCell   *cell;
+
+	attrTup = SearchSysCacheAttName(relid, coldef->colname);
+	if (!HeapTupleIsValid(attrTup))
+		elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
+			 coldef->colname, relid);
+
+	attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
+
+	if (atFormOut)
+		*atFormOut = attrForm;
+
+	get_atttypetypmodcoll(relid, attrForm->attnum,
+						  &typid, &typmod, &typcollation);
+
+	/*
+	 * Search for a NOT NULL declaration. As in deparse_ColumnDef, we rely on
+	 * finding a constraint on the column rather than coldef->is_not_null.
+	 * (This routine is never used for ALTER cases.)
+	 */
+	saw_notnull = false;
+	foreach(cell, coldef->constraints)
+	{
+		Constraint *constr = (Constraint *) lfirst(cell);
+
+		if (constr->contype == CONSTR_NOTNULL)
+		{
+			saw_notnull = true;
+			break;
+		}
+	}
+
+	if (notnull)
+		*notnull = saw_notnull;
+
+	if (!saw_notnull && !attrForm->atthasdef)
+	{
+		ReleaseSysCache(attrTup);
+		return false;
+	}
+
+	ReleaseSysCache(attrTup);
+	return true;
+}
+
+/*
+ * Deparse a ColumnDef node within a typed table creation. This is simpler
+ * than the regular case, because we don't have to emit the type declaration,
+ * collation, or default. Here we only return something if the column is being
+ * declared NOT NULL.
+ *
+ * As in deparse_ColumnDef, any other constraint is processed elsewhere.
+ *
+ * Verbose syntax
+ * %{name}I WITH OPTIONS %{not_null}s %{default}s.
+ */
+static void
+deparse_ColumnDef_typed(JsonbParseState *state, Relation relation,
+						List *dpcontext, ColumnDef *coldef)
+{
+	bool		needed;
+	Form_pg_attribute attrForm;
+	bool		saw_notnull;
+	StringInfoData fmtStr;
+
+	initStringInfo(&fmtStr);
+
+	needed = deparse_ColDef_typed_needed(relation, coldef,
+										 &attrForm, &saw_notnull);
+	if (!needed)
+		return;
+
+	/* start making column object */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	appendStringInfoString(&fmtStr, "%{name}I WITH OPTIONS");
+
+	/* TYPE and NAME */
+	new_jsonb_VA(state, 2,
+				 "type", jbvString, "column",
+				 "name", jbvString, coldef->colname);
+
+	/* NOT NULL */
+	if (saw_notnull)
+	{
+		appendStringInfoString(&fmtStr, " %{not_null}s");
+		new_jsonb_VA(state, 1, "not_null", jbvString, "NOT NULL");
+	}
+
+	/* DEFAULT */
+	if (attrForm->atthasdef)
+	{
+		char	   *defstr;
+
+		appendStringInfoString(&fmtStr, " %{default}s");
+		defstr = relation_get_column_default(relation, attrForm->attnum,
+											 dpcontext);
+
+		insert_jsonb_key(state, "default");
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+		new_jsonb_VA(state, 2,
+					 "fmt", jbvString, "DEFAULT %{default}s",
+					 "default", jbvString, defstr);
+
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+	}
+
+	/* We have full fmt by now, so add jsonb element for that */
+	new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+	pfree(fmtStr.data);
+
+	/* mark the end of column object */
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	/* Generated columns are not supported on typed tables, so we are done */
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Deal with all the table elements (columns and constraints).
+ *
+ * Note we ignore constraints in the parse node here; they are extracted from
+ * system catalogs instead.
+ */
+static void
+deparse_TableElems(JsonbParseState *state, Relation relation,
+				   List *tableElements, List *dpcontext,
+				   bool typed, bool composite)
+{
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					if (typed)
+						deparse_ColumnDef_typed(state, relation,
+												dpcontext,
+												(ColumnDef *) elt);
+					else
+						deparse_ColumnDef(state, relation, dpcontext,
+										  composite, (ColumnDef *) elt);
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Given a table OID, obtain its constraints and append them to the given
+ * JsonbParseState.
+ *
+ * This works for typed tables, regular tables.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static void
+deparse_Constraints(JsonbParseState *state, Oid relationId)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+
+	Assert(OidIsValid(relationId));
+
+	/*
+	 * Scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = table_open(ConstraintRelationId, AccessShareLock);
+	ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(relationId));
+	scan = systable_beginscan(conRel, ConstraintRelidTypidNameIndexId, true,
+							  NULL, 1, &key);
+
+	/*
+	 * For each constraint, add a node to the list of table elements.  In
+	 * these nodes we include not only the printable information ("fmt"), but
+	 * also separate attributes to indicate the type of constraint, for
+	 * automatic processing.
+	 */
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_constraint constrForm;
+		char	   *contype;
+		StringInfoData fmtStr;
+
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+
+		switch (constrForm->contype)
+		{
+			case CONSTRAINT_CHECK:
+				contype = "check";
+				break;
+			case CONSTRAINT_FOREIGN:
+				continue;		/* not here */
+			case CONSTRAINT_PRIMARY:
+				contype = "primary key";
+				break;
+			case CONSTRAINT_UNIQUE:
+				contype = "unique";
+				break;
+			case CONSTRAINT_EXCLUSION:
+				contype = "exclusion";
+				break;
+			default:
+				elog(ERROR, "unrecognized constraint type");
+		}
+
+		/* No need to deparse constraints inherited from parent table. */
+		if (OidIsValid(constrForm->conparentid))
+			continue;
+
+		/*
+		 * "type" and "contype" are not part of the printable output, but are
+		 * useful to programmatically distinguish these from columns and among
+		 * different constraint types.
+		 *
+		 * XXX it might be useful to also list the column names in a PK, etc.
+		 */
+		initStringInfo(&fmtStr);
+		appendStringInfoString(&fmtStr, "CONSTRAINT %{name}I %{definition}s");
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+		new_jsonb_VA(state, 4,
+					 "type", jbvString, "constraint",
+					 "contype", jbvString, contype,
+					 "name", jbvString, NameStr(constrForm->conname),
+					 "definition", jbvString,
+					 pg_get_constraintdef_string(constrForm->oid));
+
+		if (constrForm->conindid &&
+			(constrForm->contype == CONSTRAINT_PRIMARY ||
+			 constrForm->contype == CONSTRAINT_UNIQUE ||
+			 constrForm->contype == CONSTRAINT_EXCLUSION))
+		{
+			Oid			tblspc = get_rel_tablespace(constrForm->conindid);
+
+			if (OidIsValid(tblspc))
+			{
+				char	   *tblspcname = get_tablespace_name(tblspc);
+
+				if (!tblspcname)
+					elog(ERROR, "cache lookup failed for tablespace %u",
+						 tblspc);
+
+				appendStringInfoString(&fmtStr,
+									   " USING INDEX TABLESPACE %{tblspc}s");
+				new_jsonb_VA(state, 1,
+							 "tblspc", jbvString, tblspcname);
+			}
+		}
+
+		/* We have full fmt by now, so add jsonb element for that */
+		new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+		pfree(fmtStr.data);
+
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+	}
+
+	systable_endscan(scan);
+	table_close(conRel, AccessShareLock);
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Insert columns and constraints elements(if any) in output JsonbParseState
+ */
+static void
+add_table_elems(JsonbParseState *state, StringInfo fmtStr,
+					   Relation relation, List *tableElts, List *dpcontext,
+					   Oid objectId, bool inherit, bool typed, bool composite)
+{
+	insert_jsonb_key(state, "table_elements");
+
+	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+	/*
+	 * Process table elements: column definitions and constraints. Only the
+	 * column definitions are obtained from the parse node itself. To get
+	 * constraints we rely on pg_constraint, because the parse node might be
+	 * missing some things such as the name of the constraints.
+	 */
+	deparse_TableElems(state, relation, tableElts, dpcontext,
+					   typed,	/* typed table */
+					   composite);	/* not composite */
+
+	deparse_Constraints(state, objectId);
+
+	/*
+	 * Decide if we need to put '()' around table_elements. It is needed for
+	 * below cases:
+	 *
+	 * a) where actual table-elements are present, eg: create table t1 (a int)
+	 *
+	 * a) inherit case with no local table-elements present, eg: create table
+	 * t1 () inherits (t2)
+	 *
+	 * OTOH, '()' is not needed for below cases when no table-elements are
+	 * present:
+	 *
+	 * a) 'partition of' case, eg: create table t2 partition of t1
+	 *
+	 * b) 'of type' case, eg: create table t1 of type1;
+	 */
+	if ((state->contVal.type == jbvArray) &&
+		(inherit || (state->contVal.val.array.nElems > 0)))
+	{
+		appendStringInfoString(fmtStr, " (%{table_elements:, }s)");
+	}
+	else
+		appendStringInfoString(fmtStr, " %{table_elements:, }s");
+
+	/* end of table_elements array */
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+}
+
+/*
+ * Deparse DefElems, as used by Create Table
+ *
+ * Verbose syntax
+ * %{label}s = %{value}L
+ * where label is: %{schema}I %{label}I
+ */
+static void
+deparse_DefElem(JsonbParseState *state, DefElem *elem, bool is_reset)
+{
+	StringInfoData fmtStr;
+	StringInfoData labelfmt;
+
+	initStringInfo(&fmtStr);
+
+	appendStringInfoString(&fmtStr, "%{label}s");
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	/* LABEL */
+	initStringInfo(&labelfmt);
+
+	insert_jsonb_key(state, "label");
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	if (elem->defnamespace != NULL)
+	{
+		appendStringInfoString(&labelfmt, "%{schema}I.");
+		new_jsonb_VA(state, 1,
+					 "schema", jbvString, elem->defnamespace);
+	}
+
+	appendStringInfoString(&labelfmt, "%{label}I");
+	new_jsonb_VA(state, 2,
+				 "label", jbvString, elem->defname,
+				 "fmt", jbvString, labelfmt.data);
+	pfree(labelfmt.data);
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	/* VALUE */
+	if (!is_reset)
+	{
+		appendStringInfoString(&fmtStr, " = %{value}L");
+		new_jsonb_VA(state, 1, "value", jbvString,
+					 elem->arg ? defGetString(elem) :
+					 defGetBoolean(elem) ? "true" : "false");
+	}
+
+	new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+	pfree(fmtStr.data);
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse WITH clause, as used by Create Table.
+ *
+ * Verbose syntax (formulated in helper function deparse_DefElem)
+ * %{label}s = %{value}L
+ */
+static void
+deparse_withObj(JsonbParseState *state, CreateStmt *node)
+{
+	ListCell   *cell;
+
+	/* WITH */
+	insert_jsonb_key(state, "with");
+	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+	/* add elements to array */
+	foreach(cell, node->options)
+	{
+		DefElem    *opt = (DefElem *) lfirst(cell);
+
+		deparse_DefElem(state, opt, false);
+	}
+
+	/* with's array end */
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+}
+
+/*
+ * Deparse a CreateStmt (CREATE TABLE).
+ *
+ * Given a table OID and the parse tree that created it, return JsonbValue
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D [OF
+ * %{of_type}T | PARTITION OF %{parent_identity}D] %{table_elements}s
+ * %{inherits}s %{partition_bound}s %{partition_by}s %{access_method}s
+ * %{with_clause}s %{tablespace}s
+ */
+static Jsonb *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	Oid			nspid = relation->rd_rel->relnamespace;
+	char	   *relname = RelationGetRelationName(relation);
+	List	   *dpcontext;
+	char	   *perstr;
+	StringInfoData fmtStr;
+	JsonbParseState *state = NULL;
+	JsonbValue *value;
+
+	initStringInfo(&fmtStr);
+
+	/* mark the begin of ROOT object and start adding elements to it. */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	appendStringInfoString(&fmtStr, "CREATE");
+
+	/* PERSISTENCE */
+	perstr = get_persistence_str(relation->rd_rel->relpersistence);
+	if (perstr)
+	{
+		appendStringInfoString(&fmtStr, " %{persistence}s");
+		new_jsonb_VA(state, 1,
+					 "persistence", jbvString, perstr);
+	}
+
+	appendStringInfoString(&fmtStr, " TABLE");
+
+	/* IF NOT EXISTS */
+	if (node->if_not_exists)
+	{
+		appendStringInfoString(&fmtStr, " %{if_not_exists}s");
+		new_jsonb_VA(state, 1,
+					 "if_not_exists", jbvString, "IF NOT EXISTS");
+	}
+
+	/* IDENTITY */
+	appendStringInfoString(&fmtStr, " %{identity}D");
+	insert_identity_object(state, nspid, relname);
+
+	dpcontext = deparse_context_for(RelationGetRelationName(relation),
+									objectId);
+
+	/*
+	 * TABLE-ELEMENTS array creation
+	 */
+	if (node->ofTypename || node->partbound)
+	{
+		/* Insert the "of type" or "partition of" clause whichever present */
+		if (node->ofTypename)
+		{
+			appendStringInfoString(&fmtStr, " OF %{of_type}T");
+			new_jsonb_for_type(state, "of_type",
+							   relation->rd_rel->reloftype, -1);
+		}
+		else
+		{
+			List	   *parents;
+			Oid			objid;
+
+			appendStringInfoString(&fmtStr, " PARTITION OF %{parent_identity}D");
+			parents = relation_get_inh_parents(objectId);
+			objid = linitial_oid(parents);
+			Assert(list_length(parents) == 1);
+			new_jsonb_for_qualname_id(state, RelationRelationId,
+									  objid, "parent_identity", true);
+		}
+
+		add_table_elems(state, &fmtStr, relation,
+							   node->tableElts, dpcontext, objectId,
+							   false,	/* not inherit */
+							   true,	/* typed table */
+							   false);	/* not composite */
+	}
+	else
+	{
+		List	   *inhrelations;
+
+		/*
+		 * There is no need to process LIKE clauses separately; they have
+		 * already been transformed into columns and constraints.
+		 */
+
+		add_table_elems(state, &fmtStr, relation,
+							   node->tableElts, dpcontext, objectId,
+							   true,	/* inherit */
+							   false,	/* not typed table */
+							   false);	/* not composite */
+
+		/*
+		 * Add inheritance specification.  We cannot simply scan the list of
+		 * parents from the parser node, because that may lack the actual
+		 * qualified names of the parent relations.  Rather than trying to
+		 * re-resolve them from the information in the parse node, it seems
+		 * more accurate and convenient to grab it from pg_inherits.
+		 */
+		if (node->inhRelations != NIL)
+		{
+			appendStringInfoString(&fmtStr, " %{inherits}s");
+			insert_jsonb_key(state, "inherits");
+
+			pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+			new_jsonb_VA(state, 1, "fmt", jbvString, "INHERITS (%{parents:, }D)");
+			inhrelations = relation_get_inh_parents(objectId);
+
+			new_jsonbArray_for_qualname_id(state, "parents", inhrelations);
+			pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+		}
+	}
+
+	/* FOR VALUES clause */
+	if (node->partbound)
+	{
+		appendStringInfoString(&fmtStr, " %{partition_bound}s");
+
+		/*
+		 * Get pg_class.relpartbound. We cannot use partbound in the parsetree
+		 * directly as it's the original partbound expression which haven't
+		 * been transformed.
+		 */
+		new_jsonb_VA(state, 1,
+					 "partition_bound", jbvString,
+					 relation_get_part_bound(objectId));
+	}
+
+	/* PARTITION BY clause */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		appendStringInfoString(&fmtStr, " %{partition_by}s");
+		insert_jsonb_key(state, "partition_by");
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+		new_jsonb_VA(state, 2,
+					 "fmt", jbvString, "PARTITION BY %{definition}s",
+					 "definition", jbvString,
+					 pg_get_partkeydef_string(objectId));
+
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+	}
+
+	/* USING clause */
+	if (node->accessMethod)
+	{
+		appendStringInfoString(&fmtStr, " %{access_method}s");
+		insert_jsonb_key(state, "access_method");
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+		new_jsonb_VA(state, 2,
+					 "fmt", jbvString, "USING %{access_method}I",
+					 "access_method", jbvString, node->accessMethod);
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+	}
+
+	/* WITH clause */
+	if (node->options)
+	{
+		appendStringInfoString(&fmtStr, " %{with_clause}s");
+		insert_jsonb_key(state, "with_clause");
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+		new_jsonb_VA(state, 1,
+					 "fmt", jbvString, "WITH (%{with:, }s)");
+
+		deparse_withObj(state, node);
+
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	}
+
+	/* TABLESPACE */
+	if (node->tablespacename)
+	{
+		appendStringInfoString(&fmtStr, " %{tablespace}s");
+		insert_jsonb_key(state, "tablespace");
+		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+		new_jsonb_VA(state, 2,
+					 "fmt", jbvString, "TABLESPACE %{tablespace}I",
+					 "tablespace", jbvString, node->tablespacename);
+		pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+	}
+
+	relation_close(relation, AccessShareLock);
+
+	/* We have full fmt by now, so add jsonb element for that */
+	new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+	pfree(fmtStr.data);
+
+	/* Mark the end of ROOT object */
+	value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * Deparse a DropStmt (DROP TABLE).
+ *
+ * Given an object identity and the parse tree that created it, return
+ * jsonb string representing the drop command.
+ *
+ * Verbose syntax
+ * DROP TABLE %{concurrently}s %{if_exists}s %{objidentity}s %{cascade}s
+ */
+char *
+deparse_drop_table(const char *objidentity, Node *parsetree)
+{
+	DropStmt   *node = (DropStmt *) parsetree;
+	StringInfoData fmtStr;
+	JsonbValue *jsonbval;
+	Jsonb	   *jsonb;
+	StringInfoData str;
+	JsonbParseState *state = NULL;
+
+	initStringInfo(&str);
+	initStringInfo(&fmtStr);
+
+	/* mark the begin of ROOT object and start adding elements to it. */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	/* Start constructing fmt string */
+	appendStringInfoString(&fmtStr, "DROP TABLE");
+
+	/* CONCURRENTLY */
+	if (node->concurrent)
+	{
+		appendStringInfoString(&fmtStr, " %{concurrently}s");
+		new_jsonb_VA(state, 1,
+					 "concurrently", jbvString, "CONCURRENTLY");
+	}
+
+	/* IF EXISTS */
+	if (node->missing_ok)
+	{
+		appendStringInfoString(&fmtStr, " %{if_exists}s");
+		new_jsonb_VA(state, 1, "if_exists", jbvString, "IF EXISTS");
+	}
+
+	/* IDENTITY */
+	appendStringInfoString(&fmtStr, " %{objidentity}s");
+	new_jsonb_VA(state, 1, "objidentity", jbvString, objidentity);
+
+	/* CASCADE */
+	if (node->behavior == DROP_CASCADE)
+	{
+		appendStringInfoString(&fmtStr, " %{cascade}s");
+		new_jsonb_VA(state, 1, "cascade", jbvString, "CASCADE");
+	}
+
+	/* We have full fmt by now, so add jsonb element for that */
+	new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+	pfree(fmtStr.data);
+
+	jsonbval = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	jsonb = JsonbValueToJsonb(jsonbval);
+	return JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+}
+
+/*
+ * Deparse a CreateSeqStmt.
+ *
+ * Given a sequence OID and the parse tree that created it, return Jsonb
+ * representing the creation command.
+ *
+ * Note: We need to deparse the CREATE SEQUENCE command for the TABLE
+ * commands. For example, When creating a table, if we specify a column as a
+ * serial type, then we will create a sequence for that column and set that
+ * sequence OWNED BY the table. The serial column type information is not
+ * available during deparsing phase as that has already been converted to
+ * the column default value and sequences creation while parsing.
+ *
+ * Verbose syntax
+ * CREATE %{persistence}s SEQUENCE %{if_not_exists}s %{identity}D %{definition: }s
+ */
+static Jsonb *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	Relation	relation;
+	Form_pg_sequence seqform;
+	Sequence_values *seqvalues;
+	CreateSeqStmt *createSeqStmt = (CreateSeqStmt *) parsetree;
+	JsonbParseState *state = NULL;
+	JsonbValue *value;
+	StringInfoData fmtStr;
+	char	   *perstr;
+
+	/*
+	 * Only support sequence for IDENTITY COLUMN output separately (via CREATE
+	 * TABLE or ALTER TABLE). Otherwise, return empty here.
+	 */
+	if (createSeqStmt->for_identity)
+		return NULL;
+
+	initStringInfo(&fmtStr);
+	relation = relation_open(objectId, AccessShareLock);
+
+	/* mark the start of ROOT object */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	appendStringInfoString(&fmtStr, "CREATE");
+
+	/* PERSISTENCE */
+	perstr = get_persistence_str(relation->rd_rel->relpersistence);
+	if (perstr)
+	{
+		appendStringInfoString(&fmtStr, " %{persistence}s");
+		new_jsonb_VA(state, 1,
+					 "persistence", jbvString, perstr);
+	}
+
+	appendStringInfoString(&fmtStr, " SEQUENCE");
+
+	/* IF NOT EXISTS */
+	if (createSeqStmt->if_not_exists)
+	{
+		appendStringInfoString(&fmtStr, " %{if_not_exists}s");
+		new_jsonb_VA(state, 1,
+					 "if_not_exists", jbvString, "IF NOT EXISTS");
+	}
+
+	/* IDENTITY */
+	appendStringInfoString(&fmtStr, " %{identity}D");
+	insert_identity_object(state, relation->rd_rel->relnamespace,
+						   RelationGetRelationName(relation));
+
+	relation_close(relation, AccessShareLock);
+
+	seqvalues = get_sequence_values(objectId);
+	seqform = seqvalues->seqform;
+
+	/* sequence definition array object creation, push the key first */
+	appendStringInfoString(&fmtStr, " %{definition: }s");
+	insert_jsonb_key(state, "definition");
+
+	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+	/* Definition elements */
+	deparse_Seq_Cache(state, seqform);
+	deparse_Seq_Cycle(state, seqform);
+	deparse_Seq_IncrementBy(state, seqform);
+	deparse_Seq_Minvalue(state, seqform);
+	deparse_Seq_Maxvalue(state, seqform);
+	deparse_Seq_Startwith(state, seqform);
+	deparse_Seq_Restart(state, seqvalues->last_value);
+	deparse_Seq_As(state, seqform);
+	/* We purposefully do not emit OWNED BY here */
+
+	/* mark the end of sequence definition array */
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+	/* We have full fmt by now, so add jsonb element for that */
+	new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+	pfree(fmtStr.data);
+
+	/* mark the end of ROOT object */
+	value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * Deparse an AlterSeqStmt.
+ *
+ * Given a sequence OID and a parse tree that modified it, return Jsonb
+ * representing the alter command.
+ *
+ * Note: We need to deparse the ALTER SEQUENCE command for the TABLE commands.
+ * For example, When creating a table, if we specify a column as a serial
+ * type, then we will create a sequence for that column and set that sequence
+ * OWNED BY the table. The serial column type information is not available
+ * during deparsing phase as that has already been converted to the column
+ * default value and sequences creation while parsing.
+ *
+ * Verbose syntax
+ * ALTER SEQUENCE %{identity}D %{definition: }s
+ */
+static Jsonb *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+	Relation	relation;
+	ListCell   *cell;
+	Form_pg_sequence seqform;
+	Sequence_values *seqvalues;
+	AlterSeqStmt *alterSeqStmt = (AlterSeqStmt *) parsetree;
+	JsonbParseState *state = NULL;
+	JsonbValue *value;
+
+	/*
+	 * Sequence for IDENTITY COLUMN output separately (via CREATE TABLE or
+	 * ALTER TABLE); return empty here.
+	 */
+	if (alterSeqStmt->for_identity)
+		return NULL;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	/* mark the start of ROOT object */
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	new_jsonb_VA(state, 1,
+				 "fmt", jbvString, "ALTER SEQUENCE %{identity}D %{definition: }s");
+
+	insert_identity_object(state, relation->rd_rel->relnamespace,
+						   RelationGetRelationName(relation));
+	relation_close(relation, AccessShareLock);
+
+	seqvalues = get_sequence_values(objectId);
+	seqform = seqvalues->seqform;
+
+	/* sequence definition array object creation, push the key first */
+	insert_jsonb_key(state, "definition");
+
+	/* mark the start of sequence definition array */
+	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+	foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+	{
+		DefElem    *elem = (DefElem *) lfirst(cell);
+
+		if (strcmp(elem->defname, "cache") == 0)
+			deparse_Seq_Cache(state, seqform);
+		else if (strcmp(elem->defname, "cycle") == 0)
+			deparse_Seq_Cycle(state, seqform);
+		else if (strcmp(elem->defname, "increment") == 0)
+			deparse_Seq_IncrementBy(state, seqform);
+		else if (strcmp(elem->defname, "minvalue") == 0)
+			deparse_Seq_Minvalue(state, seqform);
+		else if (strcmp(elem->defname, "maxvalue") == 0)
+			deparse_Seq_Maxvalue(state, seqform);
+		else if (strcmp(elem->defname, "start") == 0)
+			deparse_Seq_Startwith(state, seqform);
+		else if (strcmp(elem->defname, "restart") == 0)
+			deparse_Seq_Restart(state, seqvalues->last_value);
+		else if (strcmp(elem->defname, "owned_by") == 0)
+			deparse_Seq_OwnedBy(state, objectId);
+		else if (strcmp(elem->defname, "as") == 0)
+			deparse_Seq_As(state, seqform);
+		else
+			elog(ERROR, "invalid sequence option %s", elem->defname);
+	}
+
+	/* mark the end of sequence definition array */
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+	/* mark the end of ROOT object */
+	value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function should cover all cases handled in ProcessUtilitySlow.
+ */
+static Jsonb *
+deparse_simple_command(CollectedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.address.objectId;
+
+	if (cmd->in_extension && (nodeTag(parsetree) != T_CreateExtensionStmt))
+		return NULL;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_AlterSeqStmt:
+			return deparse_AlterSeqStmt(objectId, parsetree);
+
+		case T_CreateSeqStmt:
+			return deparse_CreateSeqStmt(objectId, parsetree);
+
+		case T_CreateStmt:
+			return deparse_CreateStmt(objectId, parsetree);
+
+		default:
+			elog(LOG, "unrecognized node type in deparse command: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return NULL;
+}
+
+/*
+ * Workhorse to deparse a CollectedCommand.
+ */
+char *
+deparse_utility_command(CollectedCommand *cmd)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext oldcxt;
+	MemoryContext tmpcxt;
+	char	   *command = NULL;
+	StringInfoData str;
+	Jsonb	   *jsonb;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code, but
+	 * allocate the output StringInfo before switching.
+	 */
+	initStringInfo(&str);
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * to obtain deparsed versions of expressions.  In such results, we want
+	 * all object names to be qualified, so that results are "portable" to
+	 * environments with different search_path settings.  Rather than
+	 * injecting what would be repetitive calls to override search path all
+	 * over the place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = true;
+	PushOverrideSearchPath(overridePath);
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			jsonb = deparse_simple_command(cmd);
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	PopOverrideSearchPath();
+
+	if (jsonb)
+		command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+
+	/*
+	 * Clean up.  Note that since we created the StringInfo in the caller's
+	 * context, the output string is not deleted here.
+	 */
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(tmpcxt);
+
+	return command;
+}
+
+/*
+ * Given a CollectedCommand, return a JSON representation of it.
+ *
+ * The command is expanded fully so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+Datum
+ddl_deparse_to_json(PG_FUNCTION_ARGS)
+{
+	CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+	char	   *command;
+
+	command = deparse_utility_command(cmd);
+
+	if (command)
+		PG_RETURN_TEXT_P(cstring_to_text(command));
+
+	PG_RETURN_NULL();
+}
diff --git a/src/backend/commands/ddljson.c b/src/backend/commands/ddljson.c
new file mode 100644
index 0000000000..d5c968b7c1
--- /dev/null
+++ b/src/backend/commands/ddljson.c
@@ -0,0 +1,759 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddljson.c
+ *	  JSON code related to DDL command deparsing
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *
+ * Each JSONB object is supposed to have a "fmt" which will tell expansion
+ * routines how JSONB can be expanded to construct ddl command. One example
+ * snippet from JSONB object for 'ALTER TABLE sales ADD col1 int':
+ *
+ * { *1-level*
+ *   "fmt": "ALTER %{objtype}s %{only}s %{identity}D %{subcmds:, }s",
+ *   "only": "",
+ *  "objtype": "TABLE",
+ *  "identity": {"objname": "sales", "schemaname": "public"}
+ *  "subcmds": [
+ *      { *2-level*
+ *          "fmt": "ADD %{objtype}s %{if_not_exists}s %{definition}s",
+ *          "type": "add column",
+ *          "objtype": "COLUMN",
+ *          "definition": {}
+ *           ...
+ *      }
+ *      ...
+ * }
+ *
+ * From above, we can see different key-value pairs.
+ * level-1 represents ROOT object with 'fmt', 'only', 'objtype','identity',
+ * 'subcmds' as the keys with the values appended after ":" with each key.
+ * Value can be string, bool, numeric, array or any nested object.  As an
+ * example, "objtype" has string value while "subcmds" has nested-object
+ * as its value which can further have multiple key-value pairs.
+ *
+ * The value of "fmt" tells us how the expansion will be carried on. The
+ * value of "fmt"  may contain zero or more %-escapes, which consist of key
+ * name enclosed in { }, followed by a conversion specifier which tells us
+ * how the value for that particular key should be expanded.
+ * Possible conversion specifiers are:
+ * %            expand to a literal %
+ * I            expand as a single, non-qualified identifier
+ * D            expand as a possibly-qualified identifier
+ * T            expand as a type name
+ * L            expand as a string literal (quote using single quotes)
+ * s            expand as a simple string (no quoting)
+ * n            expand as a simple number (no quoting)
+ *
+ * In order to build a DDL command, it will first extract "fmt" node in
+ * jsonb string and will read each key name enclosed in { } in fmt-string
+ * and will replace it with its value. For each name mentioned in { } in
+ * fmt string, there must be a key-value pair, in absence of which, the
+ * expansion will error out. While doing this expansion, it will consider
+ * the conversion-specifier maintained with each key in fmt string to figure
+ * out how value should actually be represented. This is how DDL command can
+ * be constructed back from the jsonb-string.
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/ddljson.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "tcop/ddldeparse.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+
+#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \
+	do { \
+		if (++(ptr) >= (end_ptr)) \
+			ereport(ERROR, \
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+					errmsg("unterminated format specifier")); \
+	} while (0)
+
+/*
+ * Conversion specifier which determines how to expand the JSON element
+ * into a string.
+ */
+typedef enum
+{
+	SpecDottedName,
+	SpecIdentifier,
+	SpecNumber,
+	SpecString,
+	SpecStringLiteral,
+	SpecTypeName
+} convSpecifier;
+
+/*
+ * A ternary value that represents a boolean type JsonbValue.
+ */
+typedef enum
+{
+	tv_absent,
+	tv_true,
+	tv_false
+}			json_trivalue;
+
+static bool expand_one_jsonb_element(StringInfo buf, char *param,
+									 JsonbValue *jsonval, convSpecifier specifier,
+									 const char *fmt);
+static void expand_jsonb_array(StringInfo buf, char *param,
+							   JsonbValue *jsonarr, char *arraysep,
+							   convSpecifier specifier, const char *fmt);
+static void fmtstr_error_callback(void *arg);
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvBool, an error is raised. If it doesn't
+ * exist, tv_absent is returned; otherwise return the actual json_trivalue.
+ */
+static json_trivalue
+find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	json_trivalue result;
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+		return tv_absent;
+	if (value->type != jbvBool)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" is not of type boolean", keyname));
+	result = value->val.boolean ? tv_true : tv_false;
+	pfree(value);
+
+	return result;
+}
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvString, an error is raised.  If it doesn't
+ * exist, an error is raised unless missing_ok; otherwise return NULL.
+ *
+ * If it exists and is a string, a freshly palloc'ed copy is returned.
+ *
+ * If *length is not NULL, it is set to the length of the string.
+ */
+static char *
+find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname,
+							  bool missing_ok, int *length)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	char	   *str;
+
+	/* XXX verify that this is an object, not an array */
+
+	key.type = jbvString;
+	key.val.string.val = keyname;
+	key.val.string.len = strlen(keyname);
+	value = findJsonbValueFromContainer(container,
+										JB_FOBJECT, &key);
+	if (value == NULL)
+	{
+		if (missing_ok)
+			return NULL;
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("missing element \"%s\" in JSON object", keyname));
+	}
+
+	if (value->type != jbvString)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" is not of type string", keyname));
+
+	str = pnstrdup(value->val.string.val, value->val.string.len);
+	if (length)
+		*length = value->val.string.len;
+	pfree(value);
+	return str;
+}
+
+/*
+ * Recursive helper for deparse_ddl_json_to_string.
+ *
+ * Find the "fmt" element in the given container, and expand it into the
+ * provided StringInfo.
+ */
+static void
+expand_fmt_recursive(StringInfo buf, JsonbContainer *container)
+{
+	JsonbValue	key;
+	JsonbValue *value;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	int			len;
+
+	start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len);
+	end_ptr = start_ptr + len;
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array = false;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(buf, *cp);
+			continue;
+		}
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(buf, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after a colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			while (cp < end_ptr)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * Found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("missing conversion name in conversion specifier"));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypeName;
+				break;
+			case 'n':
+				specifier = SpecNumber;
+				break;
+			default:
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("invalid conversion specifier \"%c\"", *cp));
+		}
+
+		/*
+		 * Obtain the element to be expanded.
+		 */
+		key.type = jbvString;
+		key.val.string.val = param;
+		key.val.string.len = strlen(param);
+
+		value = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
+		Assert(value != NULL);
+
+		/*
+		 * Expand the data (possibly an array) into the output StringInfo.
+		 */
+		if (is_array)
+			expand_jsonb_array(buf, param, value, arraysep, specifier, start_ptr);
+		else
+			expand_one_jsonb_element(buf, param, value, specifier, start_ptr);
+
+		pfree(value);
+	}
+}
+
+/*
+ * Expand a json value as a quoted identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+
+	Assert(jsonval->type == jbvString);
+
+	str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+	appendStringInfoString(buf, quote_identifier(str));
+	pfree(str);
+}
+
+/*
+ * Expand a json value as a dot-separated-name.  The value must be of type
+ * binary and may contain elements "schemaname" (optional), "objname"
+ * (mandatory), "attrname" (optional).  Double quotes are added to each element
+ * as necessary, and dot separators where needed.
+ *
+ * One day we might need a "catalog" element as well, but no current use case
+ * needs that.
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+	JsonbContainer *data = jsonval->val.binary.data;
+
+	Assert(jsonval->type == jbvBinary);
+
+	str = find_string_in_jsonbcontainer(data, "schemaname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, "%s.", quote_identifier(str));
+		pfree(str);
+	}
+
+	str = find_string_in_jsonbcontainer(data, "objname", false, NULL);
+	appendStringInfo(buf, "%s", quote_identifier(str));
+	pfree(str);
+
+	str = find_string_in_jsonbcontainer(data, "attrname", true, NULL);
+	if (str)
+	{
+		appendStringInfo(buf, ".%s", quote_identifier(str));
+		pfree(str);
+	}
+}
+
+/*
+ * Expand a JSON value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename = NULL;
+	char	   *typmodstr = NULL;
+	json_trivalue is_array;
+	char	   *array_decor;
+	JsonbContainer *data = jsonval->val.binary.data;
+
+	/*
+	 * We omit schema-qualifying the output name if the schema element is
+	 * either the empty string or NULL; the difference between those two cases
+	 * is that in the latter we quote the type name, in the former we don't.
+	 * This allows for types with special typmod needs, such as interval and
+	 * timestamp (see format_type_detailed), while at the same time allowing
+	 * for the schema name to be omitted from type names that require quotes
+	 * but are to be obtained from a user schema.
+	 */
+
+	schema = find_string_in_jsonbcontainer(data, "schemaname", true, NULL);
+	typename = find_string_in_jsonbcontainer(data, "typename", false, NULL);
+	typmodstr = find_string_in_jsonbcontainer(data, "typmod", true, NULL);
+	is_array = find_bool_in_jsonbcontainer(data, "typarray");
+	switch (is_array)
+	{
+		case tv_true:
+			array_decor = "[]";
+			break;
+
+		case tv_false:
+			array_decor = "";
+			break;
+
+		case tv_absent:
+		default:
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("missing typarray element"));
+	}
+
+	if (schema == NULL)
+		appendStringInfo(buf, "%s", quote_identifier(typename));
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s", typename);	/* Special typmod needs */
+	else
+		appendStringInfo(buf, "%s.%s", quote_identifier(schema),
+						 quote_identifier(typename));
+
+	appendStringInfo(buf, "%s%s", typmodstr ? typmodstr : "", array_decor);
+
+	if (schema)
+		pfree(schema);
+	if (typename)
+		pfree(typename);
+	if (typmodstr)
+		pfree(typmodstr);
+}
+
+/*
+ * Expand a JSON value as a string.  The value must be of type string or of
+ * type Binary.  In the latter case, it must contain a "fmt" element which will
+ * be recursively expanded; also, if the object contains an element "present"
+ * and it is set to false, the expansion is the empty string.
+ *
+ * Returns false if no actual expansion was made due to the "present" flag
+ * being set to "false".
+ *
+ * The caller is responsible to check jsonval is of type jbvString or jbvBinary.
+ */
+static bool
+expand_jsonval_string(StringInfo buf, JsonbValue *jsonval)
+{
+	bool expanded = false;
+
+	Assert((jsonval->type == jbvString) || (jsonval->type == jbvBinary));
+
+	if (jsonval->type == jbvString)
+	{
+		appendBinaryStringInfo(buf, jsonval->val.string.val,
+							   jsonval->val.string.len);
+		expanded = true;
+	}
+	else if (jsonval->type == jbvBinary)
+	{
+		json_trivalue present;
+
+		present = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+											  "present");
+
+		/*
+		 * If "present" is set to false, this element expands to empty;
+		 * otherwise (either true or absent), expand "fmt".
+		 */
+		if (present != tv_false)
+		{
+			expand_fmt_recursive(buf, jsonval->val.binary.data);
+			expanded = true;
+		}
+	}
+
+	return expanded;
+}
+
+/*
+ * Expand a JSON value as a string literal.
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *str;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int			dqnextchar = 0;
+
+	Assert(jsonval->type == jbvString);
+
+	str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+
+	/* Easy case: if there are no ' and no \, just use a single quote */
+	if (strpbrk(str, "\'\\") == NULL)
+	{
+		appendStringInfo(buf, "'%s'", str);
+		pfree(str);
+		return;
+	}
+
+	/* Otherwise need to find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(str, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar = dqnextchar % (sizeof(dqsuffixes) - 1);
+	}
+	/* Add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
+	pfree(dqdelim.data);
+	pfree(str);
+}
+
+/*
+ * Expand a JSON value as an integer quantity.
+ */
+static void
+expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
+{
+	char	   *strdatum;
+
+	Assert(jsonval->type == jbvNumeric);
+
+	strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
+												   NumericGetDatum(jsonval->val.numeric)));
+	appendStringInfoString(buf, strdatum);
+	pfree(strdatum);
+}
+
+/*
+ * Expand one JSON element into the output StringInfo according to the
+ * conversion specifier.  The element type is validated, and an error is raised
+ * if it doesn't match what we expect for the conversion specifier.
+ *
+ * Returns true, except for the formatted string case if no actual expansion
+ * was made (due to the "present" flag being set to "false").
+ */
+static bool
+expand_one_jsonb_element(StringInfo buf, char *param, JsonbValue *jsonval,
+						 convSpecifier specifier, const char *fmt)
+{
+	bool		string_expanded = true;
+	ErrorContextCallback sqlerrcontext;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonval)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" not found", param));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON string for %%I element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_identifier(buf, jsonval);
+			break;
+
+		case SpecDottedName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON struct for %%D element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_dottedname(buf, jsonval);
+			break;
+
+		case SpecString:
+			if (jsonval->type != jbvString &&
+				jsonval->type != jbvBinary)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON string or struct for %%s element \"%s\", got %d",
+							   param, jsonval->type));
+			string_expanded = expand_jsonval_string(buf, jsonval);
+			break;
+
+		case SpecStringLiteral:
+			if (jsonval->type != jbvString)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON string for %%L element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_strlit(buf, jsonval);
+			break;
+
+		case SpecTypeName:
+			if (jsonval->type != jbvBinary)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON struct for %%T element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_typename(buf, jsonval);
+			break;
+
+		case SpecNumber:
+			if (jsonval->type != jbvNumeric)
+				ereport(ERROR,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("expected JSON numeric for %%n element \"%s\", got %d",
+							   param, jsonval->type));
+			expand_jsonval_number(buf, jsonval);
+			break;
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+
+	return string_expanded;
+}
+
+/*
+ * Iterate on the elements of a JSON array, expanding each one into the output
+ * StringInfo per the given conversion specifier, separated by the given
+ * separator.
+ */
+static void
+expand_jsonb_array(StringInfo buf, char *param,
+				   JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
+				   const char *fmt)
+{
+	ErrorContextCallback sqlerrcontext;
+	JsonbContainer *container;
+	JsonbIterator *it;
+	JsonbValue	v;
+	int			type;
+	bool		first = true;
+	StringInfoData arrayelem;
+
+	/* If we were given a format string, setup an ereport() context callback */
+	if (fmt)
+	{
+		sqlerrcontext.callback = fmtstr_error_callback;
+		sqlerrcontext.arg = (void *) fmt;
+		sqlerrcontext.previous = error_context_stack;
+		error_context_stack = &sqlerrcontext;
+	}
+
+	if (!jsonarr)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" not found", param));
+
+	if (jsonarr->type != jbvBinary)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" is not a JSON array", param));
+
+	container = jsonarr->val.binary.data;
+	if (!JsonContainerIsArray(container))
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("element \"%s\" is not a JSON array", param));
+
+	initStringInfo(&arrayelem);
+
+	it = JsonbIteratorInit(container);
+	while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+	{
+		if (type == WJB_ELEM)
+		{
+			resetStringInfo(&arrayelem);
+
+			if (expand_one_jsonb_element(&arrayelem, param, &v, specifier, NULL))
+			{
+				if (!first)
+					appendStringInfoString(buf, arraysep);
+
+				appendBinaryStringInfo(buf, arrayelem.data, arrayelem.len);
+				first = false;
+			}
+		}
+	}
+
+	if (fmt)
+		error_context_stack = sqlerrcontext.previous;
+}
+
+/*
+ * Workhorse for ddl_deparse_expand_command.
+ */
+char *
+deparse_ddl_json_to_string(char *json_str)
+{
+	Datum		d;
+	Jsonb	   *jsonb;
+	StringInfo	buf = (StringInfo) palloc0(sizeof(StringInfoData));
+
+	initStringInfo(buf);
+
+	d = DirectFunctionCall1(jsonb_in, PointerGetDatum(json_str));
+	jsonb = (Jsonb *) DatumGetPointer(d);
+
+	expand_fmt_recursive(buf, &jsonb->root);
+
+	return buf->data;
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.  Possible conversion specifiers are:
+ *
+ * %		expand to a literal %
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ * n		expand as a simple number (no quoting)
+ *
+ * The element name may have an optional separator specification preceded
+ * by a colon.  Its presence indicates that the element is expected to be
+ * an array; the specified separator is used to join the array elements.
+ *
+ * The actual conversion of single JSON element into string according to
+ * above conversion specifiers takes place in expand_one_jsonb_element()
+ *------
+ */
+Datum
+ddl_deparse_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	char	   *json_str;
+
+	json_str = text_to_cstring(json);
+
+	PG_RETURN_TEXT_P(cstring_to_text(deparse_ddl_json_to_string(json_str)));
+}
+
+/*
+ * Error context callback for JSON format string expansion.
+ *
+ * XXX: indicate which element we're expanding, if applicable.
+ */
+static void
+fmtstr_error_callback(void *arg)
+{
+	errcontext("while expanding format string \"%s\"", (char *) arg);
+}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index d4b00d1a82..4d48e490ed 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -37,6 +37,7 @@
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
+#include "tcop/ddldeparse.h"
 #include "tcop/deparse_utility.h"
 #include "tcop/utility.h"
 #include "utils/acl.h"
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 42cced9ebe..9539e53bac 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -17,6 +17,8 @@ backend_sources += files(
   'copyto.c',
   'createas.c',
   'dbcommands.c',
+  'ddldeparse.c',
+  'ddljson.c',
   'define.c',
   'discard.c',
   'dropcmds.c',
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ef01449678..4bb731d5ff 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1707,6 +1707,49 @@ process_owned_by(Relation seqrel, List *owned_by, bool for_identity)
 		relation_close(tablerel, NoLock);
 }
 
+/*
+ * Return sequence parameters, detailed
+ */
+Sequence_values *
+get_sequence_values(Oid sequenceId)
+{
+	Buffer      buf;
+	SeqTable    elm;
+	Relation    seqrel;
+	HeapTuple	seqtuple;
+	HeapTupleData seqtupledata;
+	Form_pg_sequence seqform;
+	Form_pg_sequence_data seq;
+	Sequence_values *seqvalues;
+
+	seqtuple = SearchSysCache1(SEQRELID, sequenceId);
+	if (!HeapTupleIsValid(seqtuple))
+		elog(ERROR, "cache lookup failed for sequence %u", sequenceId);
+	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+
+	ReleaseSysCache(seqtuple);
+
+	/* Open and lock sequence */
+	init_sequence(sequenceId, &elm, &seqrel);
+
+	if (pg_class_aclcheck(sequenceId, GetUserId(),
+		ACL_SELECT | ACL_USAGE) != ACLCHECK_OK)
+		ereport(ERROR,
+				errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				errmsg("permission denied for sequence %s",
+					   RelationGetRelationName(seqrel)));
+
+	seq = read_seq_tuple(seqrel, &buf, &seqtupledata);
+
+	seqvalues = (Sequence_values *) palloc(sizeof(Sequence_values));
+	seqvalues->last_value = seq->last_value;
+	seqvalues->seqform = seqform;
+
+	UnlockReleaseBuffer(buf);
+	relation_close(seqrel, NoLock);
+
+	return seqvalues;
+}
 
 /*
  * Return sequence parameters in a list of the form created by the parser.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d985278ac6..604c1de474 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -600,7 +600,6 @@ static ObjectAddress ATExecSetCompression(Relation rel,
 										  const char *column, Node *newValue, LOCKMODE lockmode);
 
 static void index_copy_data(Relation rel, RelFileLocator newrlocator);
-static const char *storage_name(char c);
 
 static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 											Oid oldRelOid, void *arg);
@@ -2266,7 +2265,7 @@ truncate_check_activity(Relation rel)
  * storage_name
  *	  returns the name corresponding to a typstorage/attstorage enum value
  */
-static const char *
+char *
 storage_name(char c)
 {
 	switch (c)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index d67580fc77..753456ecbc 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1393,6 +1393,7 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 		atcmd->cmds = atsubcmds;
 		atcmd->objtype = OBJECT_TABLE;
 		atcmd->missing_ok = false;
+		atcmd->table_like = true;
 		result = lcons(atcmd, result);
 	}
 
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 7c5d9110fb..67c228a424 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -4978,3 +4978,31 @@ satisfies_hash_partition(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(rowHash % modulus == remainder);
 }
+
+/*
+ * Obtain the deparsed partition bound expression for the given table.
+ */
+char *
+relation_get_part_bound(Oid relid)
+{
+	Datum		deparsed;
+	Datum		bound;
+	bool		isnull;
+	HeapTuple	tuple;
+
+	tuple = SearchSysCache1(RELOID, relid);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation with OID %u", relid);
+
+	bound = SysCacheGetAttr(RELOID, tuple,
+							Anum_pg_class_relpartbound,
+							&isnull);
+
+	deparsed = DirectFunctionCall2(pg_get_expr,
+								   CStringGetTextDatum(TextDatumGetCString(bound)),
+								   relid);
+
+	ReleaseSysCache(tuple);
+
+	return TextDatumGetCString(deparsed);
+}
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 12402a0637..4318129558 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -27,8 +27,6 @@
 #include "utils/numeric.h"
 #include "utils/syscache.h"
 
-static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
-
 
 /*
  * SQL function: format_type(type_oid, typemod)
@@ -329,6 +327,110 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 	return buf;
 }
 
+/*
+ * Similar to format_type_extended, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; the caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name in this case.
+ *
+ * - typname is set to the type name, without quotes
+ *
+ * - typemodstr is set to the typemod, if any, as a string with parentheses
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *typearray)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type with OID %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * We switch our attention to the array element type for certain cases.
+	 * Check if it's a "true" array type.  Pseudo-array types such as "name"
+	 * shouldn't get deconstructed.  Also check the toast property, and don't
+	 * deconstruct "plain storage" array types --- this is because we don't
+	 * want to show oidvector as oid[].
+	 */
+	array_base_type = typeform->typelem;
+
+	*typearray = (IsTrueArrayType(typeform) && typeform->typstorage != TYPSTORAGE_PLAIN);
+
+	if (*typearray)
+	{
+		ReleaseSysCache(tuple);
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type with OID %u", type_oid);
+
+		typeform = (Form_pg_type) GETSTRUCT(tuple);
+		type_oid = array_base_type;
+	}
+
+	/*
+	 * Special-case crock for types with strange typmod rules where we put
+	 * typemod at the middle of name (e.g. TIME(6) with time zone). We cannot
+	 * schema-qualify nor add quotes to the type name in these cases.
+	 */
+	*nspid = InvalidOid;
+
+	switch (type_oid)
+	{
+		case TIMEOID:
+			*typname = pstrdup("TIME");
+			break;
+		case TIMESTAMPOID:
+			*typname = pstrdup("TIMESTAMP");
+			break;
+		case TIMESTAMPTZOID:
+			if (typemod < 0)
+				*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+			else
+				/* otherwise, WITH TZ is added by typmod. */
+				*typname = pstrdup("TIMESTAMP");
+			break;
+		case INTERVALOID:
+			*typname = pstrdup("INTERVAL");
+			break;
+		case TIMETZOID:
+			if (typemod < 0)
+				*typname = pstrdup("TIME WITH TIME ZONE");
+			else
+				/* otherwise, WITH TZ is added by typmod. */
+				*typname = pstrdup("TIME");
+			break;
+		default:
+
+			/*
+			 * No additional processing is required for other types, so get
+			 * the type name and schema directly from the catalog.
+			 */
+			*nspid = typeform->typnamespace;
+			*typname = pstrdup(NameStr(typeform->typname));
+	}
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod("", typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
 /*
  * This version is for use within the backend in error messages, etc.
  * One difference is that it will fail for an invalid type.
@@ -363,7 +465,7 @@ format_type_with_typemod(Oid type_oid, int32 typemod)
 /*
  * Add typmod decoration to the basic type name
  */
-static char *
+char *
 printTypmod(const char *typname, int32 typmod, Oid typmodout)
 {
 	char	   *res;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d3a973d86b..2d1961f12d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -51,7 +51,6 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
-#include "parser/parse_node.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "parser/parser.h"
@@ -501,22 +500,15 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
 									   deparse_context *context);
 static void get_tablesample_def(TableSampleClause *tablesample,
 								deparse_context *context);
-static void get_opclass_name(Oid opclass, Oid actual_datatype,
-							 StringInfo buf);
 static Node *processIndirection(Node *node, deparse_context *context);
 static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context);
 static char *get_relation_name(Oid relid);
 static char *generate_relation_name(Oid relid, List *namespaces);
 static char *generate_qualified_relation_name(Oid relid);
-static char *generate_function_name(Oid funcid, int nargs,
-									List *argnames, Oid *argtypes,
-									bool has_variadic, bool *use_variadic_p,
-									ParseExprKind special_exprkind);
 static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
 static void add_cast_to(StringInfo buf, Oid typid);
 static char *generate_qualified_type_name(Oid typid);
 static text *string_to_text(char *str);
-static char *flatten_reloptions(Oid relid);
 static void get_reloptions(StringInfo buf, Datum reloptions);
 
 #define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
@@ -1901,6 +1893,14 @@ pg_get_partkeydef_columns(Oid relid, bool pretty)
 	return pg_get_partkeydef_worker(relid, prettyFlags, true, false);
 }
 
+/* Internal version that reports the full partition key definition */
+char *
+pg_get_partkeydef_string(Oid relid)
+{
+	return pg_get_partkeydef_worker(relid, GET_PRETTY_FLAGS(false), false,
+									false);
+}
+
 /*
  * Internal workhorse to decompile a partition key definition.
  */
@@ -2148,6 +2148,16 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(res));
 }
 
+/*
+ * Internal version that returns the definition of a CONSTRAINT command
+ */
+char *
+pg_get_constraintdef_string(Oid constraintId)
+{
+	return pg_get_constraintdef_worker(constraintId, false,
+									   GET_PRETTY_FLAGS(false), false);
+}
+
 /*
  * Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command
  */
@@ -11758,7 +11768,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
  * actual_datatype.  (If you don't want this behavior, just pass
  * InvalidOid for actual_datatype.)
  */
-static void
+void
 get_opclass_name(Oid opclass, Oid actual_datatype,
 				 StringInfo buf)
 {
@@ -12152,7 +12162,7 @@ generate_qualified_relation_name(Oid relid)
  *
  * The result includes all necessary quoting and schema-prefixing.
  */
-static char *
+char *
 generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
 					   bool has_variadic, bool *use_variadic_p,
 					   ParseExprKind special_exprkind)
@@ -12538,7 +12548,7 @@ get_reloptions(StringInfo buf, Datum reloptions)
 /*
  * Generate a C string representing a relation's reloptions, or NULL if none.
  */
-static char *
+char *
 flatten_reloptions(Oid relid)
 {
 	char	   *result = NULL;
@@ -12606,3 +12616,23 @@ get_range_partbound_string(List *bound_datums)
 
 	return buf->data;
 }
+
+/*
+ * Obtain the deparsed default value for the given column of the given table.
+ *
+ * Caller must have set a correct deparse context and must ensure that the
+ * passed attribute has a default value.
+ */
+char *
+relation_get_column_default(Relation rel, AttrNumber attno, List *dpcontext)
+{
+	Node	   *defval;
+	char	   *defstr;
+
+	defval = build_column_default(rel, attno);
+	Assert(defval != NULL);
+
+	defstr = deparse_expression(defval, dpcontext, false, false);
+
+	return defstr;
+}
diff --git a/src/include/catalog/pg_inherits.h b/src/include/catalog/pg_inherits.h
index ce154ab943..99909d2c34 100644
--- a/src/include/catalog/pg_inherits.h
+++ b/src/include/catalog/pg_inherits.h
@@ -64,4 +64,6 @@ extern bool DeleteInheritsTuple(Oid inhrelid, Oid inhparent,
 								const char *childname);
 extern bool PartitionHasPendingDetach(Oid partoid);
 
+extern List *relation_get_inh_parents(Oid objectId);
+
 #endif							/* PG_INHERITS_H */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..76ff23b779 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12043,4 +12043,11 @@
   proname => 'any_value_transfn', prorettype => 'anyelement',
   proargtypes => 'anyelement anyelement', prosrc => 'any_value_transfn' },
 
+{ oid => '4642', descr => 'deparse the DDL command into a JSON format string',
+  proname => 'ddl_deparse_to_json', prorettype => 'text',
+  proargtypes => 'pg_ddl_command', prosrc => 'ddl_deparse_to_json' },
+{ 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' },
+
 ]
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 7db7b3da7b..c0a39596ac 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -15,6 +15,7 @@
 
 #include "access/xlogreader.h"
 #include "catalog/objectaddress.h"
+#include "catalog/pg_sequence.h"
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "nodes/parsenodes.h"
@@ -51,9 +52,17 @@ typedef struct xl_seq_rec
 	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
 } xl_seq_rec;
 
+/* Information needed to define a sequence. */
+typedef struct Sequence_values
+{
+	Form_pg_sequence seqform;
+	int64		last_value;
+} Sequence_values;
+
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
+extern Sequence_values *get_sequence_values(Oid sequenceId);
 
 extern ObjectAddress DefineSequence(ParseState *pstate, CreateSeqStmt *seq);
 extern ObjectAddress AlterSequence(ParseState *pstate, AlterSeqStmt *stmt);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 250d89ff88..d2874b9255 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -105,4 +105,6 @@ extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
 												 List *partConstraint);
 
+extern char *storage_name(char c);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b3bec90e52..b8ab8dae6c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2167,6 +2167,7 @@ typedef struct AlterTableStmt
 	List	   *cmds;			/* list of subcommands */
 	ObjectType	objtype;		/* type of object */
 	bool		missing_ok;		/* skip error if table missing */
+	bool		table_like;		/* internally generated for TableLikeClause */
 } AlterTableStmt;
 
 typedef enum AlterTableType
diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index d2e01f92df..476b38a89e 100644
--- a/src/include/partitioning/partbounds.h
+++ b/src/include/partitioning/partbounds.h
@@ -143,4 +143,6 @@ extern int	partition_range_datum_bsearch(FmgrInfo *partsupfunc,
 extern int	partition_hash_bsearch(PartitionBoundInfo boundinfo,
 								   int modulus, int remainder);
 
+extern char    *relation_get_part_bound(Oid relid);
+
 #endif							/* PARTBOUNDS_H */
diff --git a/src/include/tcop/ddldeparse.h b/src/include/tcop/ddldeparse.h
new file mode 100644
index 0000000000..0ccb4d1ede
--- /dev/null
+++ b/src/include/tcop/ddldeparse.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddldeparse.h
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/ddldeparse.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DDL_DEPARSE_H
+#define DDL_DEPARSE_H
+
+#include "tcop/deparse_utility.h"
+
+extern char *deparse_utility_command(CollectedCommand *cmd);
+extern char *deparse_ddl_json_to_string(char *jsonb);
+extern char *deparse_drop_table(const char *objidentity, Node *parsetree);
+
+#endif							/* DDL_DEPARSE_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2f8b46d6da..cfda299dee 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -124,9 +124,14 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_INVALID_AS_NULL	0x08	/* NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
+extern void format_type_detailed(Oid type_oid, int32 typemod,
+								  Oid *nspid, char **typname,
+								  char **typemodstr, bool *typearray);
+
 extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
+extern char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
 
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
 
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index b006d9d475..50a8a14d11 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -16,6 +16,7 @@
 #include "nodes/nodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
+#include "parser/parse_node.h"
 
 struct Plan;					/* avoid including plannodes.h here */
 struct PlannedStmt;
@@ -31,9 +32,11 @@ extern char *pg_get_indexdef_columns_extended(Oid indexrelid,
 extern char *pg_get_querydef(Query *query, bool pretty);
 
 extern char *pg_get_partkeydef_columns(Oid relid, bool pretty);
+extern char *pg_get_partkeydef_string(Oid relid);
 extern char *pg_get_partconstrdef_string(Oid partitionId, char *aliasname);
 
 extern char *pg_get_constraintdef_command(Oid constraintId);
+extern char *pg_get_constraintdef_string(Oid constraintId);
 extern char *deparse_expression(Node *expr, List *dpcontext,
 								bool forceprefix, bool showimplicit);
 extern List *deparse_context_for(const char *aliasname, Oid relid);
@@ -45,8 +48,18 @@ extern List *select_rtable_names_for_explain(List *rtable,
 											 Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
 extern char *generate_opclass_name(Oid opclass);
+extern char *generate_function_name(Oid funcid, int nargs, List *argnames,
+									Oid *argtypes, bool has_variadic,
+									bool *use_variadic_p,
+									ParseExprKind special_exprkind);
 extern char *get_range_partbound_string(List *bound_datums);
+extern void get_opclass_name(Oid opclass, Oid actual_datatype,
+							 StringInfo buf);
+extern char *flatten_reloptions(Oid relid);
 
 extern char *pg_get_statisticsobjdef_string(Oid statextid);
 
+extern char *relation_get_column_default(Relation rel, AttrNumber attno,
+										 List *dpcontext);
+
 #endif							/* RULEUTILS_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 260854747b..7c8ae7fe7b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3222,6 +3222,7 @@ config_var_value
 contain_aggs_of_level_context
 contain_placeholder_references_context
 convert_testexpr_context
+convSpecifier
 copy_data_dest_cb
 copy_data_source_cb
 core_YYSTYPE
@@ -3425,6 +3426,7 @@ json_manifest_perwalrange_callback
 json_ofield_action
 json_scalar_action
 json_struct_action
+json_trivalue
 keyEntryData
 key_t
 lclContext
-- 
2.34.1



  [application/octet-stream] 0002-Deparser-for-Alter-Table-DDL-commands-2023_06_30.patch (58.6K, 4-0002-Deparser-for-Alter-Table-DDL-commands-2023_06_30.patch)
  download | inline diff:
From af8d4a38c411d8d92c6c5fd275fde7e2a85209fc Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Wed, 28 Jun 2023 14:37:02 +0530
Subject: [PATCH 2/5] Deparser for Alter Table DDL commands.

This patch constructs JSON blobs representing the Alter Table
DDL commands which can later be re-processed into plain strings
by well-defined sprintf-like expansion.
---
 src/backend/commands/ddldeparse.c | 1705 ++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c        |   17 +
 src/include/tcop/utility.h        |    2 +
 3 files changed, 1683 insertions(+), 41 deletions(-)

diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c
index c8e885ca1a..cb024b45b8 100644
--- a/src/backend/commands/ddldeparse.c
+++ b/src/backend/commands/ddldeparse.c
@@ -363,11 +363,16 @@ insert_identity_object(JsonbParseState *state, Oid nspid, char *relname)
  * CACHE %{value}
  */
 static inline void
-deparse_Seq_Cache(JsonbParseState *state, Form_pg_sequence seqdata)
+deparse_Seq_Cache(JsonbParseState *state, Form_pg_sequence seqdata,
+				  bool alter_table)
 {
+	char	   *fmt;
+
+	fmt = alter_table ? "SET CACHE %{value}s" : "CACHE %{value}s";
+
 	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
 	new_jsonb_VA(state, 3,
-				 "fmt", jbvString, "CACHE %{value}s",
+				 "fmt", jbvString, fmt,
 				 "clause", jbvString, "cache",
 				 "value", jbvString,
 				 psprintf(INT64_FORMAT, seqdata->seqcache));
@@ -378,10 +383,13 @@ deparse_Seq_Cache(JsonbParseState *state, Form_pg_sequence seqdata)
  * Deparse the sequence CYCLE option to Jsonb.
  *
  * Verbose syntax
+ * SET %{no}s CYCLE
+ * OR
  * %{no}s CYCLE
  */
 static inline void
-deparse_Seq_Cycle(JsonbParseState *state, Form_pg_sequence seqdata)
+deparse_Seq_Cycle(JsonbParseState *state, Form_pg_sequence seqdata,
+				  bool alter_table)
 {
 	StringInfoData fmtStr;
 
@@ -389,6 +397,9 @@ deparse_Seq_Cycle(JsonbParseState *state, Form_pg_sequence seqdata)
 
 	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
 
+	if (alter_table)
+		appendStringInfoString(&fmtStr, "SET ");
+
 	if (!seqdata->seqcycle)
 	{
 		appendStringInfoString(&fmtStr, "%{no}s ");
@@ -408,14 +419,22 @@ deparse_Seq_Cycle(JsonbParseState *state, Form_pg_sequence seqdata)
  * Deparse the sequence INCREMENT BY option to Jsonb
  *
  * Verbose syntax
+ * SET INCREMENT BY %{value}s
+ * OR
  * INCREMENT BY %{value}s
  */
 static inline void
-deparse_Seq_IncrementBy(JsonbParseState *state, Form_pg_sequence seqdata)
+deparse_Seq_IncrementBy(JsonbParseState *state, Form_pg_sequence seqdata,
+						bool alter_table)
 {
+	char	   *fmt;
+
+	fmt = alter_table ? "SET INCREMENT BY %{value}s"
+		: "INCREMENT BY %{value}s";
+
 	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
 	new_jsonb_VA(state, 3,
-				 "fmt", jbvString, "INCREMENT BY %{value}s",
+				 "fmt", jbvString, fmt,
 				 "clause", jbvString, "seqincrement",
 				 "value", jbvString,
 				 psprintf(INT64_FORMAT, seqdata->seqincrement));
@@ -426,14 +445,21 @@ deparse_Seq_IncrementBy(JsonbParseState *state, Form_pg_sequence seqdata)
  * Deparse the sequence MAXVALUE option to Jsonb.
  *
  * Verbose syntax
+ * SET MAXVALUE %{value}s
+ * OR
  * MAXVALUE %{value}s
  */
 static inline void
-deparse_Seq_Maxvalue(JsonbParseState *state, Form_pg_sequence seqdata)
+deparse_Seq_Maxvalue(JsonbParseState *state, Form_pg_sequence seqdata,
+					 bool alter_table)
 {
+	char	   *fmt;
+
+	fmt = alter_table ? "SET MAXVALUE %{value}s" : "MAXVALUE %{value}s";
+
 	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
 	new_jsonb_VA(state, 3,
-				 "fmt", jbvString, "MAXVALUE %{value}s",
+				 "fmt", jbvString, fmt,
 				 "clause", jbvString, "maxvalue",
 				 "value", jbvString,
 				 psprintf(INT64_FORMAT, seqdata->seqmax));
@@ -444,14 +470,21 @@ deparse_Seq_Maxvalue(JsonbParseState *state, Form_pg_sequence seqdata)
  * Deparse the sequence MINVALUE option to Jsonb
  *
  * Verbose syntax
+ * SET MINVALUE %{value}s
+ * OR
  * MINVALUE %{value}s
  */
 static inline void
-deparse_Seq_Minvalue(JsonbParseState *state, Form_pg_sequence seqdata)
+deparse_Seq_Minvalue(JsonbParseState *state, Form_pg_sequence seqdata,
+					 bool alter_table)
 {
+	char	   *fmt;
+
+	fmt = alter_table ? "SET MINVALUE %{value}s" : "MINVALUE %{value}s";
+
 	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
 	new_jsonb_VA(state, 3,
-				 "fmt", jbvString, "MINVALUE %{value}s",
+				 "fmt", jbvString, fmt,
 				 "clause", jbvString, "minvalue",
 				 "value", jbvString,
 				 psprintf(INT64_FORMAT, seqdata->seqmin));
@@ -465,7 +498,8 @@ deparse_Seq_Minvalue(JsonbParseState *state, Form_pg_sequence seqdata)
  * OWNED BY %{owner}D
  */
 static void
-deparse_Seq_OwnedBy(JsonbParseState *state, Oid sequenceId)
+deparse_Seq_OwnedBy(JsonbParseState *state, Oid sequenceId,
+					bool alter_table)
 {
 	Relation	depRel;
 	SysScanDesc scan;
@@ -550,14 +584,21 @@ deparse_Seq_OwnedBy(JsonbParseState *state, Oid sequenceId)
  * Deparse the sequence START WITH option to Jsonb.
  *
  * Verbose syntax
+ * SET START WITH %{value}s
+ * OR
  * START WITH %{value}s
  */
 static inline void
-deparse_Seq_Startwith(JsonbParseState *state, Form_pg_sequence seqdata)
+deparse_Seq_Startwith(JsonbParseState *state,
+					  Form_pg_sequence seqdata, bool alter_table)
 {
+	char	   *fmt;
+
+	fmt = alter_table ? "SET START WITH %{value}s" : "START WITH %{value}s";
+
 	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
 	new_jsonb_VA(state, 3,
-				 "fmt", jbvString, "START WITH %{value}s",
+				 "fmt", jbvString, fmt,
 				 "clause", jbvString, "start",
 				 "value", jbvString,
 				 psprintf(INT64_FORMAT, seqdata->seqstart));
@@ -601,15 +642,17 @@ deparse_Seq_As(JsonbParseState *state, Form_pg_sequence seqdata)
  * Deparse the definition of column identity to Jsonb.
  *
  * Verbose syntax
- * %{identity_type}s ( %{seq_definition: }s )
- * where identity_type is: "GENERATED %{option}s AS IDENTITY"
+ * SET GENERATED %{option}s %{seq_definition: }s
+ * OR
+ * GENERATED %{option}s AS IDENTITY ( %{seq_definition: }s )
  */
 static void
 deparse_ColumnIdentity(JsonbParseState *state, char *parentKey,
-					   Oid seqrelid, char identity)
+					   Oid seqrelid, char identity, bool alter_table)
 {
 	Form_pg_sequence seqform;
 	Sequence_values *seqvalues;
+	char	   *identfmt;
 	StringInfoData fmtStr;
 
 	initStringInfo(&fmtStr);
@@ -631,10 +674,11 @@ deparse_ColumnIdentity(JsonbParseState *state, char *parentKey,
 		insert_jsonb_key(state, "identity_type");
 
 		pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+		identfmt = alter_table ? "SET GENERATED %{option}s" :
+			"GENERATED %{option}s AS IDENTITY";
 
 		new_jsonb_VA(state, 2,
-					 "fmt", jbvString,
-					 "GENERATED %{option}s AS IDENTITY",
+					 "fmt", jbvString, identfmt,
 					 "option", jbvString,
 					 (identity == ATTRIBUTE_IDENTITY_ALWAYS ?
 					  "ALWAYS" : "BY DEFAULT"));
@@ -644,7 +688,8 @@ deparse_ColumnIdentity(JsonbParseState *state, char *parentKey,
 	/* seq_definition array object creation */
 	insert_jsonb_key(state, "seq_definition");
 
-	appendStringInfoString(&fmtStr, " ( %{seq_definition: }s )");
+	appendStringInfoString(&fmtStr, alter_table ? " %{seq_definition: }s" :
+						   " ( %{seq_definition: }s )");
 
 	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
 
@@ -652,12 +697,12 @@ deparse_ColumnIdentity(JsonbParseState *state, char *parentKey,
 	seqform = seqvalues->seqform;
 
 	/* Definition elements */
-	deparse_Seq_Cache(state, seqform);
-	deparse_Seq_Cycle(state, seqform);
-	deparse_Seq_IncrementBy(state, seqform);
-	deparse_Seq_Minvalue(state, seqform);
-	deparse_Seq_Maxvalue(state, seqform);
-	deparse_Seq_Startwith(state, seqform);
+	deparse_Seq_Cache(state, seqform, alter_table);
+	deparse_Seq_Cycle(state, seqform, alter_table);
+	deparse_Seq_IncrementBy(state, seqform, alter_table);
+	deparse_Seq_Minvalue(state, seqform, alter_table);
+	deparse_Seq_Maxvalue(state, seqform, alter_table);
+	deparse_Seq_Startwith(state, seqform, alter_table);
 	deparse_Seq_Restart(state, seqvalues->last_value);
 	/* We purposefully do not emit OWNED BY here */
 
@@ -685,7 +730,8 @@ deparse_ColumnIdentity(JsonbParseState *state, char *parentKey,
  */
 static void
 deparse_ColumnDef(JsonbParseState *state, Relation relation,
-				  List *dpcontext, bool composite, ColumnDef *coldef)
+				  List *dpcontext, bool composite, ColumnDef *coldef,
+				  bool is_alter)
 {
 	Oid			relid = RelationGetRelid(relation);
 	HeapTuple	attrTup;
@@ -790,6 +836,9 @@ deparse_ColumnDef(JsonbParseState *state, Relation relation,
 			}
 		}
 
+		if (is_alter && coldef->is_not_null)
+			saw_notnull = true;
+
 		/* NOT NULL */
 		if (saw_notnull)
 		{
@@ -836,7 +885,7 @@ deparse_ColumnDef(JsonbParseState *state, Relation relation,
 				appendStringInfoString(&fmtStr, " %{identity_column}s");
 				deparse_ColumnIdentity(state, "identity_column",
 									   seqrelid,
-									   coldef->identity);
+									   coldef->identity, is_alter);
 			}
 		}
 
@@ -1036,7 +1085,7 @@ deparse_TableElems(JsonbParseState *state, Relation relation,
 												(ColumnDef *) elt);
 					else
 						deparse_ColumnDef(state, relation, dpcontext,
-										  composite, (ColumnDef *) elt);
+										  composite, (ColumnDef *) elt, false);
 				}
 				break;
 			case T_Constraint:
@@ -1275,6 +1324,93 @@ deparse_DefElem(JsonbParseState *state, DefElem *elem, bool is_reset)
 	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
 }
 
+/*
+ * Deparse SET/RESET as used by
+ * ALTER TABLE ... ALTER COLUMN ... SET/RESET (...)
+ *
+ * Verbose syntax
+ * ALTER COLUMN %{column}I RESET|SET (%{options:, }s)
+ */
+static void
+deparse_ColumnSetOptions(JsonbParseState *state, AlterTableCmd *subcmd)
+{
+	ListCell   *cell;
+	bool		is_reset = subcmd->subtype == AT_ResetOptions;
+	bool		elem_found PG_USED_FOR_ASSERTS_ONLY = false;
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	new_jsonb_VA(state, 3,
+				 "fmt", jbvString, "ALTER COLUMN %{column}I"
+				 " %{option}s (%{options:, }s)",
+				 "column", jbvString, subcmd->name,
+				 "option", jbvString, is_reset ? "RESET" : "SET");
+
+	insert_jsonb_key(state, "options");
+	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem    *elem;
+
+		elem = (DefElem *) lfirst(cell);
+		deparse_DefElem(state, elem, is_reset);
+
+#ifdef USE_ASSERT_CHECKING
+		elem_found = true;
+#endif
+	}
+
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+	Assert(elem_found);
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse SET/RESET as used by ALTER TABLE ... SET/RESET (...)
+ *
+ * Verbose syntax
+ * RESET|SET (%{options:, }s)
+ */
+static void
+deparse_RelSetOptions(JsonbParseState *state, AlterTableCmd *subcmd)
+{
+	ListCell   *cell;
+	bool		is_reset = subcmd->subtype == AT_ResetRelOptions;
+	bool		elem_found PG_USED_FOR_ASSERTS_ONLY = false;
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	new_jsonb_VA(state, 2,
+				 "fmt", jbvString, "%{set_reset}s (%{options:, }s)",
+				 "set_reset", jbvString, is_reset ? "RESET" : "SET");
+
+	/* insert options array */
+	insert_jsonb_key(state, "options");
+
+	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+	foreach(cell, (List *) subcmd->def)
+	{
+		DefElem    *elem;
+
+		elem = (DefElem *) lfirst(cell);
+		deparse_DefElem(state, elem, is_reset);
+
+#ifdef USE_ASSERT_CHECKING
+		elem_found = true;
+#endif
+	}
+
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+	Assert(elem_found);
+
+	pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
 /*
  * Deparse WITH clause, as used by Create Table.
  *
@@ -1575,6 +1711,1281 @@ deparse_drop_table(const char *objidentity, Node *parsetree)
 	return JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
 }
 
+/*
+ * Deparse all the collected subcommands and return jsonb string representing
+ * the alter command.
+ *
+ * Verbose syntax
+ * ALTER TABLE %{only}s %{identity}D %{subcmds:, }s
+ */
+static Jsonb *
+deparse_AlterTableStmt(CollectedCommand *cmd)
+{
+	List	   *dpcontext;
+	Relation	rel;
+	ListCell   *cell;
+	Oid			relId = cmd->d.alterTable.objectId;
+	AlterTableStmt *stmt = NULL;
+	StringInfoData fmtStr;
+	JsonbParseState *state = NULL;
+	bool		subCmdArray = false;
+	JsonbValue *value;
+
+	Assert(cmd->type == SCT_AlterTable);
+	stmt = (AlterTableStmt *) cmd->parsetree;
+
+	Assert(IsA(stmt, AlterTableStmt) || IsA(stmt, AlterTableMoveAllStmt));
+
+	initStringInfo(&fmtStr);
+
+	/*
+	 * ALTER TABLE subcommands generated for TableLikeClause is processed in
+	 * the top level CREATE TABLE command; return empty here.
+	 */
+	if (IsA(stmt, AlterTableStmt) && stmt->table_like)
+		return NULL;
+
+	rel = relation_open(relId, AccessShareLock);
+
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		/* unsupported relkind */
+		table_close(rel, AccessShareLock);
+		return NULL;
+	}
+
+	dpcontext = deparse_context_for(RelationGetRelationName(rel), relId);
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+	/* Start constructing fmt string */
+	appendStringInfoString(&fmtStr, "ALTER TABLE");
+
+	if (!stmt->relation->inh)
+	{
+		appendStringInfoString(&fmtStr, " %{only}s");
+		new_jsonb_VA(state, 1, "only", jbvString, "ONLY");
+	}
+
+	appendStringInfoString(&fmtStr, " %{identity}D");
+	insert_identity_object(state, rel->rd_rel->relnamespace,
+						   RelationGetRelationName(rel));
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
+		AlterTableCmd *subcmd = (AlterTableCmd *) sub->parsetree;
+
+		Assert(IsA(subcmd, AlterTableCmd));
+
+		/*
+		 * Skip deparse of the subcommand if the objectId doesn't match the
+		 * target relation ID. It can happen for inherited tables when
+		 * subcommands for inherited tables and the parent table are both
+		 * collected in the ALTER TABLE command for the parent table.
+		 */
+		if (subcmd->subtype != AT_AttachPartition &&
+			sub->address.objectId != relId &&
+			has_superclass(sub->address.objectId))
+			continue;
+
+		/* Mark the begin of subcmds array */
+		if (!subCmdArray)
+		{
+			appendStringInfoString(&fmtStr, " %{subcmds:, }s");
+			insert_jsonb_key(state, "subcmds");
+			pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+			subCmdArray = true;
+		}
+
+		switch (subcmd->subtype)
+		{
+			case AT_AddColumn:
+				{
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					/* XXX need to set the "recurse" bit somewhere? */
+					Assert(IsA(subcmd->def, ColumnDef));
+
+					/*
+					 * Syntax: ADD COLUMN %{if_not_exists}s %{definition}s"
+					 * where definition: "%{name}I %{coltype}T STORAGE
+					 * %{colstorage}s %{compression}s %{collation}s
+					 * %{not_null}s %{default}s %{identity_column}s
+					 * %{generated_column}s"
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+					appendStringInfoString(&fmtSub, "ADD COLUMN");
+					new_jsonb_VA(state, 1, "type", jbvString, "add column");
+
+					if (subcmd->missing_ok)
+					{
+						appendStringInfoString(&fmtSub, " %{if_not_exists}s");
+						new_jsonb_VA(state, 1,
+									 "if_not_exists", jbvString, "IF NOT EXISTS");
+					}
+
+					/* Push definition key-value pair */
+					appendStringInfoString(&fmtSub, " %{definition}s");
+					insert_jsonb_key(state, "definition");
+
+					deparse_ColumnDef(state, rel, dpcontext,
+									  false, (ColumnDef *) subcmd->def,
+									  true);
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+					pfree(fmtSub.data);
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+					break;
+				}
+
+			case AT_AddIndexConstraint:
+				{
+					IndexStmt  *istmt;
+					Relation	idx;
+					Oid			conOid = sub->address.objectId;
+
+					Assert(IsA(subcmd->def, IndexStmt));
+					istmt = (IndexStmt *) subcmd->def;
+
+					Assert(istmt->isconstraint && istmt->unique);
+
+					idx = relation_open(istmt->indexOid, AccessShareLock);
+
+					/*
+					 * Syntax: ADD CONSTRAINT %{name}I %{constraint_type}s
+					 * USING INDEX %index_name}I %{deferrable}s
+					 * %{init_deferred}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+					new_jsonb_VA(state, 7,
+								 "fmt", jbvString,
+								 "ADD CONSTRAINT %{name}I %{constraint_type}s"
+								 " USING INDEX %{index_name}I %{deferrable}s"
+								 " %{init_deferred}s",
+								 "type", jbvString, "add constraint using index",
+								 "name", jbvString, get_constraint_name(conOid),
+								 "constraint_type", jbvString,
+								 istmt->primary ? "PRIMARY KEY" : "UNIQUE",
+								 "index_name", jbvString,
+								 RelationGetRelationName(idx),
+								 "deferrable", jbvString,
+								 istmt->deferrable ? "DEFERRABLE" :
+								 "NOT DEFERRABLE",
+								 "init_deferred", jbvString,
+								 istmt->initdeferred ? "INITIALLY DEFERRED" :
+								 "INITIALLY IMMEDIATE");
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+					relation_close(idx, AccessShareLock);
+					break;
+				}
+			case AT_ReAddIndex:
+			case AT_ReAddConstraint:
+			case AT_ReAddDomainConstraint:
+			case AT_ReAddComment:
+			case AT_ReplaceRelOptions:
+			case AT_CheckNotNull:
+			case AT_ReAddStatistics:
+				/* Subtypes used for internal operations; nothing to do here */
+				break;
+
+			case AT_ColumnDefault:
+				if (subcmd->def == NULL)
+				{
+					/*
+					 * Syntax: ALTER COLUMN %{column}I DROP DEFAULT
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+					new_jsonb_VA(state, 3,
+								 "fmt", jbvString,
+								 "ALTER COLUMN %{column}I DROP DEFAULT",
+								 "type", jbvString, "drop default",
+								 "column", jbvString, subcmd->name);
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				}
+				else
+				{
+					List	   *dpcontext_rel;
+					HeapTuple	attrtup;
+					AttrNumber	attno;
+
+					dpcontext_rel = deparse_context_for(
+														RelationGetRelationName(rel),
+														RelationGetRelid(rel));
+					attrtup = SearchSysCacheAttName(RelationGetRelid(rel),
+													subcmd->name);
+					attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
+
+					/*
+					 * Syntax: ALTER COLUMN %{column}I SET DEFAULT
+					 * %{definition}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+					new_jsonb_VA(state, 4,
+								 "fmt", jbvString,
+								 "ALTER COLUMN %{column}I SET DEFAULT"
+								 " %{definition}s",
+								 "type", jbvString, "set default",
+								 "column", jbvString, subcmd->name,
+								 "definition", jbvString,
+								 relation_get_column_default(rel, attno,
+															 dpcontext_rel));
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+					ReleaseSysCache(attrtup);
+				}
+
+				break;
+
+			case AT_DropNotNull:
+
+				/*
+				 * Syntax: ALTER COLUMN %{column}I DROP NOT NULL
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString,
+							 "ALTER COLUMN %{column}I DROP NOT NULL",
+							 "type", jbvString, "drop not null",
+							 "column", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_ForceRowSecurity:
+
+				/*
+				 * Syntax: FORCE ROW LEVEL SECURITY
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 1, "fmt", jbvString,
+							 "FORCE ROW LEVEL SECURITY");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_NoForceRowSecurity:
+
+				/*
+				 * Syntax: NO FORCE ROW LEVEL SECURITY
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 1, "fmt", jbvString,
+							 "NO FORCE ROW LEVEL SECURITY");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_SetNotNull:
+
+				/*
+				 * Syntax: ALTER COLUMN %{column}I SET NOT NULL
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString,
+							 "ALTER COLUMN %{column}I SET NOT NULL",
+							 "type", jbvString, "set not null",
+							 "column", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DropExpression:
+				{
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					/*
+					 * Syntax: ALTER COLUMN %{column}I DROP EXPRESSION
+					 * %{if_exists}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+					appendStringInfoString(&fmtSub, "ALTER COLUMN"
+										   " %{column}I DROP EXPRESSION");
+					new_jsonb_VA(state, 2,
+								 "type", jbvString, "drop expression",
+								 "column", jbvString, subcmd->name);
+
+					if (subcmd->missing_ok)
+					{
+						appendStringInfoString(&fmtSub, " %{if_exists}s");
+						new_jsonb_VA(state, 1,
+									 "if_exists", jbvString, "IF EXISTS");
+					}
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+					pfree(fmtSub.data);
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+					break;
+				}
+
+			case AT_SetStatistics:
+				Assert(IsA(subcmd->def, Integer));
+
+				/*
+				 * Syntax: ALTER COLUMN %{column}I SET STATISTICS
+				 * %{statistics}n
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 4,
+							 "fmt", jbvString,
+							 "ALTER COLUMN %{column}I SET STATISTICS"
+							 " %{statistics}n",
+							 "type", jbvString, "set statistics",
+							 "column", jbvString, subcmd->name,
+							 "statistics", jbvNumeric,
+							 intVal((Integer *) subcmd->def));
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_SetOptions:
+			case AT_ResetOptions:
+
+				/*
+				 * Syntax: ALTER COLUMN %{column}I RESET|SET (%{options:, }s)
+				 */
+				deparse_ColumnSetOptions(state, subcmd);
+				break;
+
+			case AT_SetStorage:
+				Assert(IsA(subcmd->def, String));
+
+				/*
+				 * Syntax: ALTER COLUMN %{column}I SET STORAGE %{storage}s
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 4,
+							 "fmt", jbvString,
+							 "ALTER COLUMN %{column}I SET STORAGE"
+							 " %{storage}s",
+							 "type", jbvString, "set storage",
+							 "column", jbvString, subcmd->name,
+							 "storage", jbvString,
+							 strVal((String *) subcmd->def));
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_SetCompression:
+				Assert(IsA(subcmd->def, String));
+
+				/*
+				 * Syntax: ALTER COLUMN %{column}I SET COMPRESSION
+				 * %{compression_method}s
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 4,
+							 "fmt", jbvString,
+							 "ALTER COLUMN %{column}I SET COMPRESSION"
+							 " %{compression_method}s",
+							 "type", jbvString, "set compression",
+							 "column", jbvString, subcmd->name,
+							 "compression_method", jbvString,
+							 strVal((String *) subcmd->def));
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DropColumn:
+				{
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					/*
+					 * Syntax: DROP COLUMN %{if_exists}s %{column}I
+					 * %{cascade}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+					appendStringInfoString(&fmtSub, "DROP COLUMN");
+					new_jsonb_VA(state, 1, "type", jbvString, "drop column");
+
+					if (subcmd->missing_ok)
+					{
+						appendStringInfoString(&fmtSub, " %{if_exists}s");
+						new_jsonb_VA(state, 1,
+									 "if_exists", jbvString, "IF EXISTS");
+					}
+
+					appendStringInfoString(&fmtSub, " %{column}I");
+					new_jsonb_VA(state, 1, "column", jbvString, subcmd->name);
+
+					if (subcmd->behavior == DROP_CASCADE)
+					{
+						appendStringInfoString(&fmtSub, " %{cascade}s");
+						new_jsonb_VA(state, 1,
+									 "cascade", jbvString, "CASCADE");
+					}
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+					pfree(fmtSub.data);
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+					break;
+				}
+			case AT_AddIndex:
+				{
+					Oid			idxOid = sub->address.objectId;
+					IndexStmt  *istmt PG_USED_FOR_ASSERTS_ONLY =
+						(IndexStmt *) subcmd->def;
+					Relation	idx;
+					const char *idxname;
+					Oid			constrOid;
+
+					Assert(IsA(subcmd->def, IndexStmt));
+					Assert(istmt->isconstraint);
+
+					idx = relation_open(idxOid, AccessShareLock);
+					idxname = RelationGetRelationName(idx);
+
+					constrOid = get_relation_constraint_oid(
+															cmd->d.alterTable.objectId,
+															idxname, false);
+
+					/*
+					 * Syntax: ADD CONSTRAINT %{name}I %{definition}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+					new_jsonb_VA(state, 4,
+								 "fmt", jbvString,
+								 "ADD CONSTRAINT %{name}I %{definition}s",
+								 "type", jbvString, "add constraint",
+								 "name", jbvString, idxname,
+								 "definition", jbvString,
+								 pg_get_constraintdef_string(constrOid));
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;
+			case AT_AddConstraint:
+				{
+					/* XXX need to set the "recurse" bit somewhere? */
+					Oid			constrOid = sub->address.objectId;
+
+					/* Skip adding constraint for inherits table sub command */
+					if (!OidIsValid(constrOid))
+						continue;
+
+					Assert(IsA(subcmd->def, Constraint));
+
+					/*
+					 * Syntax: ADD CONSTRAINT %{name}I %{definition}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+					new_jsonb_VA(state, 4,
+								 "fmt", jbvString,
+								 "ADD CONSTRAINT %{name}I %{definition}s",
+								 "type", jbvString, "add constraint",
+								 "name", jbvString, get_constraint_name(constrOid),
+								 "definition", jbvString,
+								 pg_get_constraintdef_string(constrOid));
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+					break;
+				}
+
+			case AT_AlterConstraint:
+				{
+					Oid			conOid = sub->address.objectId;
+					Constraint *c = (Constraint *) subcmd->def;
+
+					/* If no constraint was altered, silently skip it */
+					if (!OidIsValid(conOid))
+						break;
+
+					Assert(IsA(c, Constraint));
+
+					/*
+					 * Syntax: ALTER CONSTRAINT %{name}I %{deferrable}s
+					 * %{init_deferred}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+					new_jsonb_VA(state, 5,
+								 "fmt", jbvString,
+								 "ALTER CONSTRAINT %{name}I %{deferrable}s"
+								 " %{init_deferred}s",
+								 "type", jbvString, "alter constraint",
+								 "name", jbvString, get_constraint_name(conOid),
+								 "deferrable", jbvString,
+								 c->deferrable ? "DEFERRABLE" : "NOT DEFERRABLE",
+								 "init_deferred", jbvString,
+								 c->initdeferred ? "INITIALLY DEFERRED" :
+								 "INITIALLY IMMEDIATE");
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				}
+				break;
+
+			case AT_ValidateConstraint:
+
+				/*
+				 * Syntax: VALIDATE CONSTRAINT %{constraint}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString,
+							 "VALIDATE CONSTRAINT %{constraint}I",
+							 "type", jbvString, "validate constraint",
+							 "constraint", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DropConstraint:
+				{
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					/*
+					 * Syntax: DROP CONSTRAINT %{if_exists}s %{constraint}I
+					 * %{cascade}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+					appendStringInfoString(&fmtSub, "DROP CONSTRAINT");
+					new_jsonb_VA(state, 1, "type", jbvString, "drop constraint");
+
+					if (subcmd->missing_ok)
+					{
+						appendStringInfoString(&fmtSub, " %{if_exists}s");
+						new_jsonb_VA(state, 1,
+									 "if_exists", jbvString, "IF EXISTS");
+					}
+
+					appendStringInfoString(&fmtSub, " %{constraint}I");
+					new_jsonb_VA(state, 1,
+								 "constraint", jbvString, subcmd->name);
+
+					if (subcmd->behavior == DROP_CASCADE)
+					{
+						appendStringInfoString(&fmtSub, " %{cascade}s");
+						new_jsonb_VA(state, 1, "cascade", jbvString, "CASCADE");
+					}
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+					pfree(fmtSub.data);
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				}
+				break;
+
+			case AT_AlterColumnType:
+				{
+					TupleDesc	tupdesc = RelationGetDescr(rel);
+					Form_pg_attribute att;
+					ColumnDef  *def;
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					att = &(tupdesc->attrs[sub->address.objectSubId - 1]);
+					def = (ColumnDef *) subcmd->def;
+					Assert(IsA(def, ColumnDef));
+
+					/*
+					 * Syntax: ALTER COLUMN %{column}I SET DATA TYPE
+					 * %{datatype}T %{collation}s %{using}s where using: USING
+					 * %{expression}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+					appendStringInfoString(&fmtSub, "ALTER COLUMN %{column}I"
+										   " SET DATA TYPE %{datatype}T");
+					new_jsonb_VA(state, 2,
+								 "type", jbvString, "alter column type",
+								 "column", jbvString, subcmd->name);
+
+					new_jsonb_for_type(state, "datatype",
+									   att->atttypid, att->atttypmod);
+
+					/* Add a COLLATE clause, if needed */
+					if (OidIsValid(att->attcollation))
+					{
+						appendStringInfoString(&fmtSub, " %{collation}s");
+						insert_collate_object(state, "collation",
+											  "COLLATE %{name}D",
+											  CollationRelationId,
+											  att->attcollation, "name");
+					}
+
+					/*
+					 * If there's a USING clause, transformAlterTableStmt ran
+					 * it through transformExpr and stored the resulting node
+					 * in cooked_default, which we can use here.
+					 */
+					if (def->raw_default)
+					{
+						Datum		deparsed;
+						char	   *defexpr;
+						List	   *exprs = NIL;
+
+						exprs = lappend(exprs, def->cooked_default);
+						defexpr = nodeToString(def->cooked_default);
+						deparsed = DirectFunctionCall2(pg_get_expr,
+													   CStringGetTextDatum(defexpr),
+													   RelationGetRelid(rel));
+						appendStringInfoString(&fmtSub, " %{using}s");
+						insert_jsonb_key(state, "using");
+						pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+						new_jsonb_VA(state, 2,
+									 "fmt", jbvString, "USING %{expression}s",
+									 "expression", jbvString,
+									 TextDatumGetCString(deparsed));
+						pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+					}
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+
+					pfree(fmtSub.data);
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				}
+				break;
+
+			case AT_ChangeOwner:
+
+				/*
+				 * Syntax: OWNER TO %{owner}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString, "OWNER TO %{owner}I",
+							 "type", jbvString, "change owner",
+							 "owner", jbvString,
+							 get_rolespec_name(subcmd->newowner));
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_ClusterOn:
+
+				/*
+				 * Syntax: CLUSTER ON %{index}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString, "CLUSTER ON %{index}I",
+							 "type", jbvString, "cluster on",
+							 "index", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+
+			case AT_DropCluster:
+
+				/*
+				 * Syntax: SET WITHOUT CLUSTER
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "SET WITHOUT CLUSTER",
+							 "type", jbvString, "set without cluster");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_SetLogged:
+
+				/*
+				 * Syntax: SET LOGGED
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "SET LOGGED",
+							 "type", jbvString, "set logged");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_SetUnLogged:
+
+				/*
+				 * Syntax: SET UNLOGGED
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "SET UNLOGGED",
+							 "type", jbvString, "set unlogged");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DropOids:
+
+				/*
+				 * Syntax: SET WITHOUT OIDS
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "SET WITHOUT OIDS",
+							 "type", jbvString, "set without oids");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_SetAccessMethod:
+
+				/*
+				 * Syntax: SET ACCESS METHOD %{access_method}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString,
+							 "SET ACCESS METHOD %{access_method}I",
+							 "type", jbvString, "set access method",
+							 "access_method", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_SetTableSpace:
+
+				/*
+				 * Syntax: SET TABLESPACE %{tablespace}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString, "SET TABLESPACE %{tablespace}I",
+							 "type", jbvString, "set tablespace",
+							 "tablespace", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_SetRelOptions:
+			case AT_ResetRelOptions:
+
+				/*
+				 * Syntax: SET|RESET (%{options:, }s)
+				 */
+				deparse_RelSetOptions(state, subcmd);
+				break;
+
+			case AT_EnableTrig:
+
+				/*
+				 * Syntax: ENABLE TRIGGER %{trigger}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString, "ENABLE TRIGGER %{trigger}I",
+							 "type", jbvString, "enable trigger",
+							 "trigger", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_EnableAlwaysTrig:
+
+				/*
+				 * Syntax: ENABLE ALWAYS TRIGGER %{trigger}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString,
+							 "ENABLE ALWAYS TRIGGER %{trigger}I",
+							 "type", jbvString, "enable always trigger",
+							 "trigger", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_EnableReplicaTrig:
+
+				/*
+				 * Syntax: ENABLE REPLICA TRIGGER %{trigger}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString,
+							 "ENABLE REPLICA TRIGGER %{trigger}I",
+							 "type", jbvString, "enable replica trigger",
+							 "trigger", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DisableTrig:
+
+				/*
+				 * Syntax: DISABLE TRIGGER %{trigger}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString, "DISABLE TRIGGER %{trigger}I",
+							 "type", jbvString, "disable trigger",
+							 "trigger", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_EnableTrigAll:
+
+				/*
+				 * Syntax: ENABLE TRIGGER ALL
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "ENABLE TRIGGER ALL",
+							 "type", jbvString, "enable trigger all");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DisableTrigAll:
+
+				/*
+				 * Syntax: DISABLE TRIGGER ALL
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "DISABLE TRIGGER ALL",
+							 "type", jbvString, "disable trigger all");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_EnableTrigUser:
+
+				/*
+				 * Syntax: ENABLE TRIGGER USER
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "ENABLE TRIGGER USER",
+							 "type", jbvString, "enable trigger user");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DisableTrigUser:
+
+				/*
+				 * Syntax: DISABLE TRIGGER USER
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "DISABLE TRIGGER USER",
+							 "type", jbvString, "disable trigger user");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_EnableRule:
+
+				/*
+				 * Syntax: ENABLE RULE %{rule}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString, "ENABLE RULE %{rule}I",
+							 "type", jbvString, "enable rule",
+							 "rule", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_EnableAlwaysRule:
+
+				/*
+				 * Syntax: ENABLE ALWAYS RULE %{rule}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString, "ENABLE ALWAYS RULE %{rule}I",
+							 "type", jbvString, "enable always rule",
+							 "rule", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_EnableReplicaRule:
+
+				/*
+				 * Syntax: ENABLE REPLICA RULE %{rule}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString, "ENABLE REPLICA RULE %{rule}I",
+							 "type", jbvString, "enable replica rule",
+							 "rule", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DisableRule:
+
+				/*
+				 * Syntax: DISABLE RULE %{rule}I
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 3,
+							 "fmt", jbvString, "DISABLE RULE %{rule}I",
+							 "type", jbvString, "disable rule",
+							 "rule", jbvString, subcmd->name);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_AddInherit:
+
+				/*
+				 * Syntax: INHERIT %{parent}D
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "INHERIT %{parent}D",
+							 "type", jbvString, "inherit");
+				new_jsonb_for_qualname_id(state, RelationRelationId,
+										  sub->address.objectId, "parent", true);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DropInherit:
+
+				/*
+				 * Syntax: NO INHERIT %{parent}D
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "NO INHERIT %{parent}D",
+							 "type", jbvString, "drop inherit");
+				new_jsonb_for_qualname_id(state, RelationRelationId,
+										  sub->address.objectId, "parent", true);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_AddOf:
+
+				/*
+				 * Syntax: OF %{type_of}T
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "OF %{type_of}T",
+							 "type", jbvString, "add of");
+				new_jsonb_for_type(state, "type_of", sub->address.objectId, -1);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DropOf:
+
+				/*
+				 * Syntax: NOT OF
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "NOT OF",
+							 "type", jbvString, "not of");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_ReplicaIdentity:
+
+				/*
+				 * Syntax: REPLICA IDENTITY %{ident}s
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString,
+							 "REPLICA IDENTITY %{ident}s",
+							 "type", jbvString, "replica identity");
+				switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type)
+				{
+					case REPLICA_IDENTITY_DEFAULT:
+						new_jsonb_VA(state, 1, "ident", jbvString, "DEFAULT");
+						break;
+					case REPLICA_IDENTITY_FULL:
+						new_jsonb_VA(state, 1, "ident", jbvString, "FULL");
+						break;
+					case REPLICA_IDENTITY_NOTHING:
+						new_jsonb_VA(state, 1, "ident", jbvString, "NOTHING");
+						break;
+					case REPLICA_IDENTITY_INDEX:
+						insert_jsonb_key(state, "ident");
+						new_jsonb_VA(state, 2,
+									 "fmt", jbvString, "USING INDEX %{index}I",
+									 "index", jbvString,
+									 ((ReplicaIdentityStmt *) subcmd->def)->name);
+						break;
+				}
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_EnableRowSecurity:
+
+				/*
+				 * Syntax: ENABLE ROW LEVEL SECURITY
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString,
+							 "ENABLE ROW LEVEL SECURITY",
+							 "type", jbvString, "enable row security");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_DisableRowSecurity:
+
+				/*
+				 * Syntax: DISABLE ROW LEVEL SECURITY
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString,
+							 "DISABLE ROW LEVEL SECURITY",
+							 "type", jbvString, "disable row security");
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+
+			case AT_AttachPartition:
+				{
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					/*
+					 * Syntax: ATTACH PARTITION %{partition_identity}D
+					 * %{partition_bound}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+					appendStringInfoString(&fmtSub, "ATTACH PARTITION"
+										   " %{partition_identity}D");
+
+					new_jsonb_VA(state, 1, "type", jbvString,
+								 "attach partition");
+					new_jsonb_for_qualname_id(state, RelationRelationId,
+											  sub->address.objectId,
+											  "partition_identity", true);
+
+					if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+					{
+						appendStringInfoString(&fmtSub, " %{partition_bound}s");
+						new_jsonb_VA(state, 1,
+									 "partition_bound", jbvString,
+									 relation_get_part_bound(sub->address.objectId));
+					}
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+					pfree(fmtSub.data);
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+					break;
+				}
+			case AT_DetachPartition:
+				{
+					PartitionCmd *cmd;
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					Assert(IsA(subcmd->def, PartitionCmd));
+					cmd = (PartitionCmd *) subcmd->def;
+
+					/*
+					 * Syntax: DETACH PARTITION %{partition_identity}D
+					 * %{concurrent}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+					appendStringInfoString(&fmtSub, "DETACH PARTITION"
+										   " %{partition_identity}D");
+
+					new_jsonb_VA(state, 1, "type", jbvString, "detach partition");
+					new_jsonb_for_qualname_id(state, RelationRelationId,
+											  sub->address.objectId,
+											  "partition_identity", true);
+					if (cmd->concurrent)
+					{
+						appendStringInfoString(&fmtSub, " %{concurrent}s");
+						new_jsonb_VA(state, 1,
+									 "concurrent", jbvString, "CONCURRENTLY");
+					}
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+					pfree(fmtSub.data);
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+					break;
+				}
+			case AT_DetachPartitionFinalize:
+
+				/*
+				 * Syntax: DETACH PARTITION %{partition_identity}D FINALIZE
+				 */
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 2,
+							 "fmt", jbvString, "DETACH PARTITION"
+							 " %{partition_identity}D FINALIZE",
+							 "type", jbvString, "detach partition finalize");
+
+				new_jsonb_for_qualname_id(state, RelationRelationId,
+										  sub->address.objectId,
+										  "partition_identity", true);
+				pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				break;
+			case AT_AddIdentity:
+				{
+					AttrNumber	attnum;
+					Oid			seq_relid;
+					ColumnDef  *coldef = (ColumnDef *) subcmd->def;
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					/*
+					 * Syntax: ALTER COLUMN %{column}I %{definition}s where
+					 * definition : ADD %{identity_column}s where
+					 * identity_column: %{identity_type}s ( %{seq_definition:
+					 * }s )
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+					appendStringInfoString(&fmtSub, "ALTER COLUMN %{column}I");
+					new_jsonb_VA(state, 2,
+								 "type", jbvString, "add identity",
+								 "column", jbvString, subcmd->name);
+
+					attnum = get_attnum(RelationGetRelid(rel), subcmd->name);
+					seq_relid = getIdentitySequence(RelationGetRelid(rel),
+													attnum, true);
+
+					if (OidIsValid(seq_relid))
+					{
+
+						appendStringInfoString(&fmtSub, " %{definition}s");
+						insert_jsonb_key(state, "definition");
+
+						/* insert definition's value now */
+						pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+						new_jsonb_VA(state, 1,
+									 "fmt", jbvString, "ADD %{identity_column}s");
+
+						/* insert identity_column */
+						deparse_ColumnIdentity(state, "identity_column",
+											   seq_relid,
+											   coldef->identity, false);
+
+						/* mark definition's value end */
+						pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+					}
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+
+					pfree(fmtSub.data);
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+				}
+				break;
+			case AT_SetIdentity:
+				{
+					DefElem    *defel;
+					char		identity = 0;
+					AttrNumber	attnum;
+					Oid			seq_relid;
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					/*
+					 * Syntax: ALTER COLUMN %{column}I %{definition}s where
+					 * definition : %{identity_type}s ( %{seq_definition: }s )
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+					appendStringInfoString(&fmtSub, "ALTER COLUMN %{column}I");
+					new_jsonb_VA(state, 2,
+								 "type", jbvString, "set identity",
+								 "column", jbvString, subcmd->name);
+
+					if (subcmd->def)
+					{
+						List	   *def = (List *) subcmd->def;
+
+						Assert(IsA(subcmd->def, List));
+
+						defel = linitial_node(DefElem, def);
+						identity = defGetInt32(defel);
+					}
+
+					attnum = get_attnum(RelationGetRelid(rel), subcmd->name);
+					seq_relid = getIdentitySequence(RelationGetRelid(rel),
+													attnum, true);
+
+					if (OidIsValid(seq_relid))
+					{
+						appendStringInfoString(&fmtSub, " %{definition}s");
+						deparse_ColumnIdentity(state, "definition",
+											   seq_relid, identity,
+											   true);
+					}
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+
+					pfree(fmtSub.data);
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+					break;
+				}
+			case AT_DropIdentity:
+				{
+					StringInfoData fmtSub;
+
+					initStringInfo(&fmtSub);
+
+					/*
+					 * Syntax: ALTER COLUMN %{column}I DROP IDENTITY
+					 * %{if_exists}s
+					 */
+					pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+					appendStringInfoString(&fmtSub, "ALTER COLUMN"
+										   " %{column}I DROP IDENTITY");
+					new_jsonb_VA(state, 2,
+								 "type", jbvString, "drop identity",
+								 "column", jbvString, subcmd->name);
+
+					if (subcmd->missing_ok)
+					{
+						appendStringInfoString(&fmtSub, " %{if_exists}s");
+						new_jsonb_VA(state, 1,
+									 "if_exists", jbvString, "IF EXISTS");
+					}
+
+					/* We have full fmt by now, so add jsonb element for that */
+					new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+					pfree(fmtSub.data);
+
+					pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+					break;
+				}
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	table_close(rel, AccessShareLock);
+
+	/* if subcmds array is not even created or has 0 elements, return NULL */
+	if (!subCmdArray ||
+		((state->contVal.type == jbvArray) &&
+		 (state->contVal.val.array.nElems == 0)))
+	{
+		pfree(fmtStr.data);
+		return NULL;
+	}
+
+	/* Mark the end of subcmds array */
+	pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+	/* We have full fmt by now, so add jsonb element for that */
+	new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+	pfree(fmtStr.data);
+
+	/* Mark the end of ROOT object */
+	value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	return JsonbValueToJsonb(value);
+}
+
 /*
  * Deparse a CreateSeqStmt.
  *
@@ -1589,7 +3000,8 @@ deparse_drop_table(const char *objidentity, Node *parsetree)
  * the column default value and sequences creation while parsing.
  *
  * Verbose syntax
- * CREATE %{persistence}s SEQUENCE %{if_not_exists}s %{identity}D %{definition: }s
+ * CREATE %{persistence}s SEQUENCE %{if_not_exists}s %{identity}D
+ * %{definition: }s
  */
 static Jsonb *
 deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
@@ -1653,12 +3065,12 @@ deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
 	pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
 
 	/* Definition elements */
-	deparse_Seq_Cache(state, seqform);
-	deparse_Seq_Cycle(state, seqform);
-	deparse_Seq_IncrementBy(state, seqform);
-	deparse_Seq_Minvalue(state, seqform);
-	deparse_Seq_Maxvalue(state, seqform);
-	deparse_Seq_Startwith(state, seqform);
+	deparse_Seq_Cache(state, seqform, false);
+	deparse_Seq_Cycle(state, seqform, false);
+	deparse_Seq_IncrementBy(state, seqform, false);
+	deparse_Seq_Minvalue(state, seqform, false);
+	deparse_Seq_Maxvalue(state, seqform, false);
+	deparse_Seq_Startwith(state, seqform, false);
 	deparse_Seq_Restart(state, seqvalues->last_value);
 	deparse_Seq_As(state, seqform);
 	/* We purposefully do not emit OWNED BY here */
@@ -1737,21 +3149,21 @@ deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
 		DefElem    *elem = (DefElem *) lfirst(cell);
 
 		if (strcmp(elem->defname, "cache") == 0)
-			deparse_Seq_Cache(state, seqform);
+			deparse_Seq_Cache(state, seqform, false);
 		else if (strcmp(elem->defname, "cycle") == 0)
-			deparse_Seq_Cycle(state, seqform);
+			deparse_Seq_Cycle(state, seqform, false);
 		else if (strcmp(elem->defname, "increment") == 0)
-			deparse_Seq_IncrementBy(state, seqform);
+			deparse_Seq_IncrementBy(state, seqform, false);
 		else if (strcmp(elem->defname, "minvalue") == 0)
-			deparse_Seq_Minvalue(state, seqform);
+			deparse_Seq_Minvalue(state, seqform, false);
 		else if (strcmp(elem->defname, "maxvalue") == 0)
-			deparse_Seq_Maxvalue(state, seqform);
+			deparse_Seq_Maxvalue(state, seqform, false);
 		else if (strcmp(elem->defname, "start") == 0)
-			deparse_Seq_Startwith(state, seqform);
+			deparse_Seq_Startwith(state, seqform, false);
 		else if (strcmp(elem->defname, "restart") == 0)
 			deparse_Seq_Restart(state, seqvalues->last_value);
 		else if (strcmp(elem->defname, "owned_by") == 0)
-			deparse_Seq_OwnedBy(state, objectId);
+			deparse_Seq_OwnedBy(state, objectId, false);
 		else if (strcmp(elem->defname, "as") == 0)
 			deparse_Seq_As(state, seqform);
 		else
@@ -1767,6 +3179,207 @@ deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
 	return JsonbValueToJsonb(value);
 }
 
+/*
+ * Deparse a RenameStmt.
+ *
+ * Verbose syntax
+ * ALTER TABLE %{if_exists}s %{identity}D RENAME TO %{newname}I
+ * OR
+ * ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I
+ * TO %{newname}I
+ * OR
+ * ALTER %{objtype}s %{if_exists}s %{only}s %{identity}D RENAME COLUMN
+ * %{colname}I TO %{newname}I %{cascade}s
+ */
+
+static Jsonb *
+deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	Relation	relation;
+	Oid			schemaId;
+	JsonbParseState *state = NULL;
+	JsonbValue *value;
+
+	/*
+	 * In an ALTER .. RENAME command, we don't have the original name of the
+	 * object in system catalogs: since we inspect them after the command has
+	 * executed, the old name is already gone.  Therefore, we extract it from
+	 * the parse node.  Note we still extract the schema name from the catalog
+	 * (it might not be present in the parse node); it cannot possibly have
+	 * changed anyway.
+	 */
+	switch (node->renameType)
+	{
+		case OBJECT_TABLE:
+			relation = relation_open(address.objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+			new_jsonb_VA(state, 3,
+						 "fmt", jbvString,
+						 "ALTER TABLE %{if_exists}s %{identity}D"
+						 " RENAME TO %{newname}I",
+						 "if_exists", jbvString,
+						 node->missing_ok ? "IF EXISTS" : "",
+						 "newname", jbvString, node->newname);
+
+			insert_identity_object(state, schemaId, node->relation->relname);
+			value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_TABCONSTRAINT:
+			{
+				HeapTuple	constrtup;
+				Form_pg_constraint constform;
+
+				constrtup = SearchSysCache1(CONSTROID,
+											ObjectIdGetDatum(address.objectId));
+				if (!HeapTupleIsValid(constrtup))
+					elog(ERROR, "cache lookup failed for constraint with OID %u",
+						 address.objectId);
+				constform = (Form_pg_constraint) GETSTRUCT(constrtup);
+
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+				new_jsonb_VA(state, 4,
+							 "fmt", jbvString,
+							 "ALTER TABLE %{only}s %{identity}D RENAME"
+							 " CONSTRAINT %{oldname}I TO %{newname}I",
+							 "only", jbvString,
+							 node->relation->inh ? "" : "ONLY",
+							 "oldname", jbvString, node->subname,
+							 "newname", jbvString, node->newname);
+
+				new_jsonb_for_qualname_id(state, RelationRelationId,
+										  constform->conrelid, "identity", true);
+				value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+				ReleaseSysCache(constrtup);
+			}
+			break;
+
+		case OBJECT_COLUMN:
+			{
+				StringInfoData fmtStr;
+
+				initStringInfo(&fmtStr);
+
+				relation = relation_open(address.objectId, AccessShareLock);
+				schemaId = RelationGetNamespace(relation);
+
+				pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+				appendStringInfoString(&fmtStr, "ALTER %{objtype}s");
+
+				new_jsonb_VA(state, 1,
+							 "objtype", jbvString,
+							 stringify_objtype(node->relationType));
+
+				/* Composite types do not support IF EXISTS */
+				if (node->renameType == OBJECT_COLUMN)
+				{
+					appendStringInfoString(&fmtStr, " %{if_exists}s");
+					new_jsonb_VA(state, 1,
+								 "if_exists", jbvString,
+								 node->missing_ok ? "IF EXISTS" : "");
+				}
+
+				if (!node->relation->inh)
+				{
+					appendStringInfoString(&fmtStr, " %{only}s");
+					new_jsonb_VA(state, 1, "only", jbvString, "ONLY");
+				}
+
+				appendStringInfoString(&fmtStr, " %{identity}D RENAME COLUMN"
+									   " %{colname}I TO %{newname}I");
+				insert_identity_object(state, schemaId, node->relation->relname);
+				new_jsonb_VA(state, 2,
+							 "colname", jbvString, node->subname,
+							 "newname", jbvString, node->newname);
+
+				if (node->renameType == OBJECT_ATTRIBUTE)
+				{
+
+					if (node->behavior == DROP_CASCADE)
+					{
+						appendStringInfoString(&fmtStr, " %{cascade}s");
+						new_jsonb_VA(state, 1, "cascade", jbvString, "CASCADE");
+					}
+				}
+
+				/* We have full fmt by now, so add jsonb element for that */
+				new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+				pfree(fmtStr.data);
+
+				value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+				relation_close(relation, AccessShareLock);
+				break;
+			}
+
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	return JsonbValueToJsonb(value);
+}
+
+/*
+ * Deparse an AlterObjectSchemaStmt (ALTER TABLE... SET SCHEMA command)
+ *
+ * Given the object(table) address and the parse tree that created it, return
+ * Jsonb representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{identity}s SET SCHEMA %{newschema}I
+ */
+static Jsonb *
+deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
+							  ObjectAddress old_schema)
+{
+	AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
+	char	   *identity;
+	char	   *new_schema = node->newschema;
+	char	   *old_schname;
+	char	   *ident;
+	JsonbParseState *state = NULL;
+	JsonbValue *value;
+
+	/*
+	 * Since the command has already taken place from the point of view of
+	 * catalogs, getObjectIdentity returns the object name with the already
+	 * changed schema.  The output of our deparsing must return the original
+	 * schema name, however, so we chop the schema name off the identity
+	 * string and then prepend the quoted schema name.
+	 *
+	 * XXX This is pretty clunky. Can we do better?
+	 */
+	identity = getObjectIdentity(&address, false);
+	old_schname = get_namespace_name(old_schema.objectId);
+	if (!old_schname)
+		elog(ERROR, "cache lookup failed for schema with OID %u",
+			 old_schema.objectId);
+
+	ident = psprintf("%s%s", quote_identifier(old_schname),
+					 identity + strlen(quote_identifier(new_schema)));
+
+	pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+	new_jsonb_VA(state, 4,
+				 "fmt", jbvString,
+				 "ALTER %{objtype}s %{identity}s SET SCHEMA"
+				 " %{newschema}I",
+				 "objtype", jbvString,
+				 stringify_objtype(node->objectType),
+				 "identity", jbvString, ident,
+				 "newschema", jbvString, new_schema);
+	value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+	return JsonbValueToJsonb(value);
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -1789,6 +3402,11 @@ deparse_simple_command(CollectedCommand *cmd)
 	/* This switch needs to handle everything that ProcessUtilitySlow does */
 	switch (nodeTag(parsetree))
 	{
+		case T_AlterObjectSchemaStmt:
+			return deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
+												 parsetree,
+												 cmd->d.simple.secondaryObject);
+
 		case T_AlterSeqStmt:
 			return deparse_AlterSeqStmt(objectId, parsetree);
 
@@ -1797,6 +3415,8 @@ deparse_simple_command(CollectedCommand *cmd)
 
 		case T_CreateStmt:
 			return deparse_CreateStmt(objectId, parsetree);
+		case T_RenameStmt:
+			return deparse_RenameStmt(cmd->d.simple.address, parsetree);
 
 		default:
 			elog(LOG, "unrecognized node type in deparse command: %d",
@@ -1851,6 +3471,9 @@ deparse_utility_command(CollectedCommand *cmd)
 		case SCT_Simple:
 			jsonb = deparse_simple_command(cmd);
 			break;
+		case SCT_AlterTable:
+			jsonb = deparse_AlterTableStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 30b51bf4d3..c0f7f29747 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2206,6 +2206,23 @@ UtilityContainsQuery(Node *parsetree)
 	}
 }
 
+/*
+ * stringify_objtype
+ * 		Return the given object type as a string.
+ */
+const char *
+stringify_objtype(ObjectType objtype)
+{
+	switch (objtype)
+	{
+		case OBJECT_TABLE:
+			return "TABLE";
+		default:
+			elog(ERROR, "unsupported object type %d", objtype);
+	}
+
+	return "???";				/* keep compiler quiet */
+}
 
 /*
  * AlterObjectTypeCommandTag
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 59e64aea07..22ce3e1b6f 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -99,6 +99,8 @@ extern Query *UtilityContainsQuery(Node *parsetree);
 
 extern CommandTag CreateCommandTag(Node *parsetree);
 
+extern const char *stringify_objtype(ObjectType objtype);
+
 static inline const char *
 CreateCommandName(Node *parsetree)
 {
-- 
2.34.1



  [application/octet-stream] 0003-Enhance-the-event-trigger-to-support-DDL--2023_06_30.patch (13.5K, 5-0003-Enhance-the-event-trigger-to-support-DDL--2023_06_30.patch)
  download | inline diff:
From e73398c65df9cb39076b209235ad1b90fc7bc931 Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Wed, 21 Jun 2023 09:18:49 +0530
Subject: [PATCH 3/5] Enhance the event trigger to support DDL deparsing

ALTER TABLE can have multiple subcommands which might include DROP COLUMN
command and ALTER TYPE referring the drop column in USING expression. As the
dropped column cannot be accessed after the execution of DROP COLUMN, a special
trigger is added to handle this case before the drop column is executed.
---
 src/backend/commands/ddldeparse.c    |  11 +-
 src/backend/commands/event_trigger.c | 188 +++++++++++++++++++++------
 src/backend/commands/tablecmds.c     |  10 +-
 src/include/commands/event_trigger.h |  46 ++++++-
 src/include/tcop/deparse_utility.h   |   2 +
 5 files changed, 204 insertions(+), 53 deletions(-)

diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c
index cb024b45b8..65c2550ae5 100644
--- a/src/backend/commands/ddldeparse.c
+++ b/src/backend/commands/ddldeparse.c
@@ -2326,22 +2326,13 @@ deparse_AlterTableStmt(CollectedCommand *cmd)
 					 */
 					if (def->raw_default)
 					{
-						Datum		deparsed;
-						char	   *defexpr;
-						List	   *exprs = NIL;
-
-						exprs = lappend(exprs, def->cooked_default);
-						defexpr = nodeToString(def->cooked_default);
-						deparsed = DirectFunctionCall2(pg_get_expr,
-													   CStringGetTextDatum(defexpr),
-													   RelationGetRelid(rel));
 						appendStringInfoString(&fmtSub, " %{using}s");
 						insert_jsonb_key(state, "using");
 						pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
 						new_jsonb_VA(state, 2,
 									 "fmt", jbvString, "USING %{expression}s",
 									 "expression", jbvString,
-									 TextDatumGetCString(deparsed));
+									 sub->usingexpr);
 						pushJsonbValue(&state, WJB_END_OBJECT, NULL);
 					}
 
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 4d48e490ed..8c2a494dcb 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -36,6 +36,7 @@
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
+#include "parser/parser.h"
 #include "pgstat.h"
 #include "tcop/ddldeparse.h"
 #include "tcop/deparse_utility.h"
@@ -49,45 +50,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
-typedef struct EventTriggerQueryState
-{
-	/* memory context for this state's objects */
-	MemoryContext cxt;
-
-	/* sql_drop */
-	slist_head	SQLDropList;
-	bool		in_sql_drop;
-
-	/* table_rewrite */
-	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite
-									 * event */
-	int			table_rewrite_reason;	/* AT_REWRITE reason */
-
-	/* Support for command collection */
-	bool		commandCollectionInhibited;
-	CollectedCommand *currentCommand;
-	List	   *commandList;	/* list of CollectedCommand; see
-								 * deparse_utility.h */
-	struct EventTriggerQueryState *previous;
-} EventTriggerQueryState;
-
-static EventTriggerQueryState *currentEventTriggerState = NULL;
-
-/* Support for dropped objects */
-typedef struct SQLDropObject
-{
-	ObjectAddress address;
-	const char *schemaname;
-	const char *objname;
-	const char *objidentity;
-	const char *objecttype;
-	List	   *addrnames;
-	List	   *addrargs;
-	bool		original;
-	bool		normal;
-	bool		istemp;
-	slist_node	next;
-} SQLDropObject;
+EventTriggerQueryState *currentEventTriggerState = NULL;
 
 static void AlterEventTriggerOwner_internal(Relation rel,
 											HeapTuple tup,
@@ -1538,6 +1501,7 @@ EventTriggerAlterTableStart(Node *parsetree)
 
 	command->d.alterTable.classId = RelationRelationId;
 	command->d.alterTable.objectId = InvalidOid;
+	command->d.alterTable.rewrite = false;
 	command->d.alterTable.subcmds = NIL;
 	command->parsetree = copyObject(parsetree);
 
@@ -1571,7 +1535,7 @@ EventTriggerAlterTableRelid(Oid objectId)
  * internally, so that's all that this code needs to handle at the moment.
  */
 void
-EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
+EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address, bool rewrite)
 {
 	MemoryContext oldcxt;
 	CollectedATSubcmd *newsub;
@@ -1591,12 +1555,156 @@ EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
 	newsub->address = address;
 	newsub->parsetree = copyObject(subcmd);
 
+	currentEventTriggerState->currentCommand->d.alterTable.rewrite |= rewrite;
 	currentEventTriggerState->currentCommand->d.alterTable.subcmds =
 		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
 
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * EventTriggerAlterTypeStart
+ *		Save data about a single part of an ALTER TYPE.
+ *
+ * ALTER TABLE can have multiple subcommands which might include DROP COLUMN
+ * command and ALTER TYPE referring the drop column in USING expression.
+ * As the dropped column cannot be accessed after the execution of DROP COLUMN,
+ * a special trigger is required to handle this case before the drop column is
+ * executed.
+ */
+void
+EventTriggerAlterTypeStart(AlterTableCmd *subcmd, Relation rel)
+{
+	MemoryContext oldcxt;
+	CollectedATSubcmd *newsub;
+	ColumnDef  *def;
+	Relation	attrelation;
+	HeapTuple	heapTup;
+	Form_pg_attribute attTup;
+	AttrNumber	attnum;
+	ObjectAddress address;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(subcmd->subtype == AT_AlterColumnType);
+	Assert(currentEventTriggerState->currentCommand != NULL);
+	Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));
+
+	def = (ColumnDef *) subcmd->def;
+	Assert(IsA(def, ColumnDef));
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(CollectedATSubcmd));
+	newsub->parsetree = (Node *)copyObject(subcmd);
+
+	attrelation = table_open(AttributeRelationId, RowExclusiveLock);
+
+	/* Look up the target column */
+	heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), subcmd->name);
+	if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_COLUMN),
+				errmsg("column \"%s\" of relation \"%s\" does not exist",
+					   subcmd->name, RelationGetRelationName(rel)));
+	attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
+	attnum = attTup->attnum;
+
+	ObjectAddressSubSet(address, RelationRelationId,
+						RelationGetRelid(rel), attnum);
+	heap_freetuple(heapTup);
+	table_close(attrelation, RowExclusiveLock);
+	newsub->address = address;
+
+	if (def->raw_default)
+	{
+		OverrideSearchPath *overridePath;
+		char	   *defexpr;
+
+		/*
+		 * We want all object names to be qualified when deparsing the
+		 * expression, so that results are "portable" to environments with
+		 * different search_path settings. Rather than inject what would be
+		 * repetitive calls to override search path all over the place, we do
+		 * it centrally here.
+		 */
+		overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+		overridePath->schemas = NIL;
+		overridePath->addCatalog = false;
+		overridePath->addTemp = true;
+		PushOverrideSearchPath(overridePath);
+
+		defexpr = nodeToString(def->cooked_default);
+		newsub->usingexpr = TextDatumGetCString(DirectFunctionCall2(pg_get_expr,
+																	CStringGetTextDatum(defexpr),
+																	RelationGetRelid(rel)));
+
+		PopOverrideSearchPath();
+	}
+	else
+		newsub->usingexpr = NULL;
+
+	currentEventTriggerState->currentCommand->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTypeEnd
+ *		Finish up saving an ALTER TYPE command, and add it to command list.
+ */
+void
+EventTriggerAlterTypeEnd(Node *subcmd, ObjectAddress address, bool rewrite)
+{
+	MemoryContext oldcxt;
+	CollectedATSubcmd *newsub;
+	ListCell   *cell;
+	CollectedCommand *cmd;
+	AlterTableCmd *altsubcmd = (AlterTableCmd *)subcmd;
+
+	/* ignore if event trigger context not set, or collection disabled */
+	if (!currentEventTriggerState ||
+		currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	cmd = currentEventTriggerState->currentCommand;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(cmd != NULL);
+	Assert(OidIsValid(cmd->d.alterTable.objectId));
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
+		AlterTableCmd *collcmd = (AlterTableCmd *) sub->parsetree;
+
+		if (collcmd->subtype == altsubcmd->subtype &&
+			address.classId == sub->address.classId &&
+			address.objectId == sub->address.objectId &&
+			address.objectSubId == sub->address.objectSubId)
+		{
+			cmd->d.alterTable.rewrite |= rewrite;
+			return;
+		}
+	}
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(CollectedATSubcmd));
+	newsub->address = address;
+	newsub->parsetree = copyObject(subcmd);
+
+	cmd->d.alterTable.rewrite |= rewrite;
+	cmd->d.alterTable.subcmds = lappend(cmd->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
 /*
  * EventTriggerAlterTableEnd
  *		Finish up saving an ALTER TABLE command, and add it to command list.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 604c1de474..675ca73fda 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4742,6 +4742,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
 									  AT_PASS_UNSET, context);
 			Assert(cmd != NULL);
+
+			EventTriggerAlterTypeStart(cmd, rel);
+
 			/* Performs own recursion */
 			ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
 								  lockmode, context);
@@ -5013,6 +5016,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 {
 	ObjectAddress address = InvalidObjectAddress;
 	Relation	rel = tab->rel;
+	bool		commandCollected = false;
 
 	switch (cmd->subtype)
 	{
@@ -5136,6 +5140,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 		case AT_AlterColumnType:	/* ALTER COLUMN TYPE */
 			/* parse transformation was done earlier */
 			address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
+			EventTriggerAlterTypeEnd((Node *) cmd, address, tab->rewrite);
+			commandCollected = true;
 			break;
 		case AT_AlterColumnGenericOptions:	/* ALTER COLUMN OPTIONS */
 			address =
@@ -5308,8 +5314,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 	/*
 	 * Report the subcommand to interested event triggers.
 	 */
-	if (cmd)
-		EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
+	if (cmd && !commandCollected)
+		EventTriggerCollectAlterTableSubcmd((Node *) cmd, address, tab->rewrite);
 
 	/*
 	 * Bump the command counter to ensure the next subcommand in the sequence
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 5ed6ece555..28b3486b9e 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -16,6 +16,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "catalog/pg_event_trigger.h"
+#include "lib/ilist.h"
 #include "nodes/parsenodes.h"
 #include "tcop/cmdtag.h"
 #include "tcop/deparse_utility.h"
@@ -29,6 +30,44 @@ typedef struct EventTriggerData
 	CommandTag	tag;
 } EventTriggerData;
 
+typedef struct EventTriggerQueryState
+{
+	/* memory context for this state's objects */
+	MemoryContext cxt;
+
+	/* sql_drop */
+	slist_head	SQLDropList;
+	bool		in_sql_drop;
+
+	/* table_rewrite */
+	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite
+									 * event */
+	int			table_rewrite_reason;	/* AT_REWRITE reason */
+
+	/* Support for command collection */
+	bool		commandCollectionInhibited;
+	CollectedCommand *currentCommand;
+	List	   *commandList;	/* list of CollectedCommand; see
+								 * deparse_utility.h */
+	struct EventTriggerQueryState *previous;
+} EventTriggerQueryState;
+
+/* Support for dropped objects */
+typedef struct SQLDropObject
+{
+	ObjectAddress address;
+	const char *schemaname;
+	const char *objname;
+	const char *objidentity;
+	const char *objecttype;
+	List	   *addrnames;
+	List	   *addrargs;
+	bool		original;
+	bool		normal;
+	bool		istemp;
+	slist_node	next;
+} SQLDropObject;
+
 #define AT_REWRITE_ALTER_PERSISTENCE	0x01
 #define AT_REWRITE_DEFAULT_VAL			0x02
 #define AT_REWRITE_COLUMN_REWRITE		0x04
@@ -71,7 +110,12 @@ extern void EventTriggerCollectSimpleCommand(ObjectAddress address,
 extern void EventTriggerAlterTableStart(Node *parsetree);
 extern void EventTriggerAlterTableRelid(Oid objectId);
 extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd,
-												ObjectAddress address);
+												ObjectAddress address,
+												bool rewrite);
+
+extern void EventTriggerAlterTypeStart(AlterTableCmd *subcmd, Relation rel);
+extern void EventTriggerAlterTypeEnd(Node *subcmd, ObjectAddress address,
+									 bool rewrite);
 extern void EventTriggerAlterTableEnd(void);
 
 extern void EventTriggerCollectGrant(InternalGrant *istmt);
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index b585810b9a..1831ec9aae 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -39,6 +39,7 @@ typedef struct CollectedATSubcmd
 {
 	ObjectAddress address;		/* affected column, constraint, index, ... */
 	Node	   *parsetree;
+	char	   *usingexpr;
 } CollectedATSubcmd;
 
 typedef struct CollectedCommand
@@ -62,6 +63,7 @@ typedef struct CollectedCommand
 		{
 			Oid			objectId;
 			Oid			classId;
+			bool		rewrite;
 			List	   *subcmds;
 		}			alterTable;
 
-- 
2.34.1



reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-general@postgresql.org
  Cc: houzj.fnst@fujitsu.com, sawada.mshk@gmail.com, shveta.malik@gmail.com, amit.kapila16@gmail.com, michael@paquier.xyz, wangw.fnst@fujitsu.com, shiy.fnst@fujitsu.com, vignesh21@gmail.com, itsajin@gmail.com, runqidev@gmail.com, smithpb2250@gmail.com, tgl@sss.pgh.pa.us, ggysxcq@gmail.com, dilipbalaut@gmail.com, alvherre@alvh.no-ip.org, japinli@hotmail.com, rajesh.rs0541@gmail.com, pgsql-hackers@lists.postgresql.org, zhengli10@gmail.com
  Subject: RE: Support logical replication of DDLs
  In-Reply-To: <OS0PR01MB57163E6487EFF7378CB8E17C9438A@OS0PR01MB5716.jpnprd01.prod.outlook.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox