public inbox for pgsql-general@postgresql.org
help / color / mirror / Atom feedFrom: houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com>
To: shveta malik <shveta.malik@gmail.com>
Cc: vignesh C <vignesh21@gmail.com>
Cc: Amit Kapila <amit.kapila16@gmail.com>
Cc: Ajin Cherian <itsajin@gmail.com>
Cc: wangw.fnst@fujitsu.com <wangw.fnst@fujitsu.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: Masahiko Sawada <sawada.mshk@gmail.com>
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: Fri, 7 Apr 2023 03:22:36 +0000
Message-ID: <OS0PR01MB571643602A8B7226B558DBAA94969@OS0PR01MB5716.jpnprd01.prod.outlook.com> (raw)
In-Reply-To: <OS0PR01MB57168840ABFAB9E9BB28A54B94969@OS0PR01MB5716.jpnprd01.prod.outlook.com>
References: <CAAD30ULCxqOJp0sffm_y9jNC4BVPYv7Q7_va_JE8qyfRXkfu+g@mail.gmail.com>
<CAFPTHDaaewvYUznZD1YjUQnvHycgvgMKNK4w=V8Q-8MKTpVDrw@mail.gmail.com>
<OS3PR01MB6275025C397CC22E446B1F589EBC9@OS3PR01MB6275.jpnprd01.prod.outlook.com>
<OS0PR01MB5716765B2D38786DA3943E0294809@OS0PR01MB5716.jpnprd01.prod.outlook.com>
<CAFPTHDbAJPccMcZnOraBy14hM6qBJxYqcRwK6iV6=gtL6VZQwQ@mail.gmail.com>
<CALDaNm3NUO8ofK64N7HMtNmUP=52R8_jWzrekqAm7m7wqZjwaQ@mail.gmail.com>
<CALDaNm3XUKfD+nD1AVvSuZyUY_zRk_eyz+Pt9t13N8WXViR6pw@mail.gmail.com>
<3032112.1679865718@sss.pgh.pa.us>
<CAA4eK1K3VXfTWXbLADcH81J==7ussvNdqLFHN68sEokDPueu7w@mail.gmail.com>
<CAA4eK1++Y7a2SQq55DXT6neghZgj3j+pQ74=8zfT3k8Tkdj0ZA@mail.gmail.com>
<OS0PR01MB571638B29E37EB9A48507DAE94889@OS0PR01MB5716.jpnprd01.prod.outlook.com>
<OS0PR01MB571636F0F1307AB1A868B06A948E9@OS0PR01MB5716.jpnprd01.prod.outlook.com>
<OS0PR01MB57169AB2355A90C9FA87DE75948E9@OS0PR01MB5716.jpnprd01.prod.outlook.com>
<CALDaNm2vBN8oMv-7G=DH5rR-u40JGbR9aP4B6nwr71qw17rPFA@mail.gmail.com>
<OS0PR01MB571614EE7EEDBF7049DA4EAE94939@OS0PR01MB5716.jpnprd01.prod.outlook.com>
<CAJpy0uCSCtWwb_LyjXQT65c-UksYhp-4U0QpimtEho_wxjzkog@mail.gmail.com>
<OS0PR01MB57168840ABFAB9E9BB28A54B94969@OS0PR01MB5716.jpnprd01.prod.outlook.com>
On Friday, April 7, 2023 11:13 AM houzj.fnst@fujitsu.com <houzj.fnst@fujitsu.com>
>
> On Tuesday, April 4, 2023 7:35 PM shveta malik <shveta.malik@gmail.com>
> wrote:
> >
> > On Tue, Apr 4, 2023 at 8:43 AM houzj.fnst@fujitsu.com
> > <houzj.fnst@fujitsu.com> wrote:
> >
> > > Attach the new version patch set which did the following changes:
> > >
> >
> > Hello,
> >
> > I tried below:
> > pubnew=# ALTER PUBLICATION mypub2 SET (ddl = 'table'); ALTER
> > PUBLICATION
> >
> > pubnew=# \dRp+
> > Publication mypub2 Owner |
> > All tables
> > | All DDLs | Table DDLs |
> > --------+------------+----------+------------+---------
> > shveta | t | f | f
> > (1 row)
> >
> > I still see 'Table DDLs' as false and ddl replication did not work for this case.
>
> Thanks for reporting.
>
> Attach the new version patch which include the following changes:
> * Fix the above bug for ALTER PUBLICATION SET.
> * Modify the corresponding event trigger when user execute ALTER
> PUBLICATION SET to change the ddl option.
> * Fix a miss in pg_dump's code which causes CFbot failure.
> * Rebase the patch due to recent commit 4826759.
Sorry, there was a miss when rebasing the patch which could cause the
CFbot to fail and here is the correct patch set.
Best Regards,
Hou zj
Attachments:
[application/octet-stream] 0001-Deparser-for-Table-DDL-commands-and-exten-2023_04_07-2.patch (158.1K, 2-0001-Deparser-for-Table-DDL-commands-and-exten-2023_04_07-2.patch)
download | inline diff:
From 66ad71cd537f4ae07acaf43367171934fd2b2f65 Mon Sep 17 00:00:00 2001
From: Wang Wei <wangw.fnst@fujitsu.com>
Date: Thu, 30 Mar 2023 13:50:22 +0800
Subject: [PATCH 1/6] Deparser for Table DDL commands and extending event
triggers
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/ALTER/DROP TABLE -- Note #1, Note #2
Note: For ATTACH/DETACH PARTITION, we haven't added extra logic on
the subscriber to handle the case where the table on the publisher is a PARTITIONED
TABLE while the target table on the subscriber side is a NORMAL table. We will
research this more and improve it later.
---
src/backend/commands/Makefile | 2 +
src/backend/commands/createas.c | 10 +
src/backend/commands/ddl_deparse.c | 3391 ++++++++++++++++++++++++++
src/backend/commands/ddl_json.c | 780 ++++++
src/backend/commands/event_trigger.c | 350 ++-
src/backend/commands/meson.build | 2 +
src/backend/commands/sequence.c | 43 +
src/backend/commands/tablecmds.c | 10 +-
src/backend/parser/parse_utilcmd.c | 1 +
src/backend/tcop/utility.c | 109 +
src/backend/utils/adt/format_type.c | 4 +-
src/backend/utils/adt/ruleutils.c | 31 +-
src/backend/utils/cache/evtcache.c | 2 +
src/include/catalog/pg_proc.dat | 7 +
src/include/commands/event_trigger.h | 50 +-
src/include/commands/sequence.h | 9 +
src/include/nodes/parsenodes.h | 1 +
src/include/tcop/ddl_deparse.h | 22 +
src/include/tcop/deparse_utility.h | 12 +-
src/include/tcop/utility.h | 2 +
src/include/utils/builtins.h | 1 +
src/include/utils/evtcache.h | 3 +-
src/include/utils/ruleutils.h | 9 +
src/tools/pgindent/typedefs.list | 4 +
24 files changed, 4791 insertions(+), 64 deletions(-)
create mode 100644 src/backend/commands/ddl_deparse.c
create mode 100644 src/backend/commands/ddl_json.c
create mode 100644 src/include/tcop/ddl_deparse.h
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..171dfb2800 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -29,6 +29,8 @@ OBJS = \
copyto.o \
createas.o \
dbcommands.o \
+ ddl_deparse.o \
+ ddl_json.o \
define.o \
discard.o \
dropcmds.o \
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index e91920ca14..a7b22cb5db 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -34,6 +34,7 @@
#include "catalog/namespace.h"
#include "catalog/toasting.h"
#include "commands/createas.h"
+#include "commands/event_trigger.h"
#include "commands/matview.h"
#include "commands/prepare.h"
#include "commands/tablecmds.h"
@@ -143,6 +144,15 @@ create_ctas_internal(List *attrList, IntoClause *into)
StoreViewQuery(intoRelationAddr.objectId, query, false);
CommandCounterIncrement();
}
+ else
+ {
+ /*
+ * Fire the trigger for table_init_write after creating the table so
+ * that we can access the catalog info about the newly created table
+ * in the trigger function.
+ */
+ EventTriggerTableInitWrite((Node *) create, intoRelationAddr);
+ }
return intoRelationAddr;
}
diff --git a/src/backend/commands/ddl_deparse.c b/src/backend/commands/ddl_deparse.c
new file mode 100644
index 0000000000..6efe84498f
--- /dev/null
+++ b/src/backend/commands/ddl_deparse.c
@@ -0,0 +1,3391 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_deparse.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.
+ *
+ * Deparse object tree is created by using:
+ * a) new_objtree("know contents") where the complete tree content is known or
+ * the initial tree content is known.
+ * b) new_objtree("") for the syntax where the object tree will be derived
+ * based on some conditional checks.
+ * c) new_objtree_VA where the complete tree can be derived using some fixed
+ * content or by using the initial tree content along with some variable
+ * arguments.
+ *
+ * IDENTIFICATION
+ * src/backend/commands/ddl_deparse.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/relation.h"
+#include "access/table.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_conversion.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_extension.h"
+#include "catalog/pg_foreign_data_wrapper.h"
+#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_language.h"
+#include "catalog/pg_largeobject.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_opfamily.h"
+#include "catalog/pg_policy.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_range.h"
+#include "catalog/pg_rewrite.h"
+#include "catalog/pg_sequence.h"
+#include "catalog/pg_statistic_ext.h"
+#include "catalog/pg_transform.h"
+#include "catalog/pg_ts_config.h"
+#include "catalog/pg_ts_dict.h"
+#include "catalog/pg_ts_parser.h"
+#include "catalog/pg_ts_template.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "commands/tablespace.h"
+#include "foreign/foreign.h"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/optimizer.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/ddl_deparse.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.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
+
+/*
+ * Before they are turned into JSONB representation, each command is
+ * represented as an object tree, using the structs below.
+ */
+typedef enum
+{
+ ObjTypeNull,
+ ObjTypeBool,
+ ObjTypeString,
+ ObjTypeArray,
+ ObjTypeInteger,
+ ObjTypeFloat,
+ ObjTypeObject
+} ObjType;
+
+typedef enum
+{
+ ConstrObjTable,
+ ConstrObjDomain,
+ ConstrObjForeignTable
+} ConstraintObjType;
+
+/*
+ * Represent the command as an object tree.
+ */
+typedef struct ObjTree
+{
+ slist_head params; /* Object tree parameters */
+ int numParams; /* Number of parameters in the object tree */
+ StringInfo fmtinfo; /* Format string of the ObjTree */
+ bool present; /* Indicates if boolean value should be stored */
+} ObjTree;
+
+/*
+ * An element of an object tree (ObjTree).
+ */
+typedef struct ObjElem
+{
+ char *name; /* Name of object element */
+ ObjType objtype; /* Object type */
+
+ union
+ {
+ bool boolean;
+ char *string;
+ int64 integer;
+ float8 flt;
+ ObjTree *object;
+ List *array;
+ } value; /* Store the object value based on the object
+ * type */
+ slist_node node; /* Used in converting back to ObjElem
+ * structure */
+} ObjElem;
+
+/*
+ * Reduce some unnecessary strings from the output json when verbose
+ * and "present" member is false. This means these strings won't be merged into
+ * the last DDL command.
+ */
+bool verbose = true;
+
+static void append_array_object(ObjTree *tree, char *sub_fmt, List *array);
+static void append_bool_object(ObjTree *tree, char *sub_fmt, bool value);
+static void append_null_object(ObjTree *tree, char *sub_fmt);
+static void append_object_object(ObjTree *tree, char *sub_fmt, ObjTree *value);
+static char *append_object_to_format_string(ObjTree *tree, char *sub_fmt);
+static void append_premade_object(ObjTree *tree, ObjElem *elem);
+static void append_string_object(ObjTree *tree, char *sub_fmt, char *name,
+ char *value);
+static void format_type_detailed(Oid type_oid, int32 typemod,
+ Oid *nspid, char **typname, char **typemodstr,
+ bool *typarray);
+static ObjElem *new_object(ObjType type, char *name);
+static ObjTree *new_objtree_for_qualname_id(Oid classId, Oid objectId);
+static ObjElem *new_object_object(ObjTree *value);
+static ObjTree *new_objtree_VA(char *fmt, int numobjs,...);
+static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+static char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
+ List *dpcontext, List **exprs);
+
+static ObjTree *deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
+ ColumnDef *coldef, bool is_alter, List **exprs);
+static ObjTree *deparse_ColumnIdentity(Oid seqrelid, char identity, bool alter_table);
+static ObjTree *deparse_ColumnSetOptions(AlterTableCmd *subcmd);
+
+static ObjTree *deparse_DefElem(DefElem *elem, bool is_reset);
+static ObjTree *deparse_OnCommitClause(OnCommitAction option);
+static ObjTree *deparse_RelSetOptions(AlterTableCmd *subcmd);
+
+static inline ObjElem *deparse_Seq_Cache(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_Cycle(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_IncrementBy(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_Minvalue(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_Maxvalue(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_Restart(int64 last_value);
+static inline ObjElem *deparse_Seq_Startwith(Form_pg_sequence seqdata, bool alter_table);
+static inline ObjElem *deparse_Seq_As(Form_pg_sequence seqdata);
+static inline ObjElem *deparse_Type_Storage(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Receive(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Send(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Typmod_In(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Typmod_Out(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Analyze(Form_pg_type typForm);
+static inline ObjElem *deparse_Type_Subscript(Form_pg_type typForm);
+
+static List *deparse_InhRelations(Oid objectId);
+static List *deparse_TableElements(Relation relation, List *tableElements, List *dpcontext,
+ bool typed, bool composite);
+
+/*
+ * Append present as false to a tree.
+ */
+static void
+append_not_present(ObjTree *tree)
+{
+ append_bool_object(tree, "present", false);
+}
+
+/*
+ * Append an array parameter to a tree.
+ */
+static void
+append_array_object(ObjTree *tree, char *sub_fmt, List *array)
+{
+ ObjElem *param;
+ char *object_name;
+
+ Assert(sub_fmt);
+
+ if (list_length(array) == 0)
+ return;
+
+ if (!verbose)
+ {
+ ListCell *lc;
+
+ /* Remove elements where present flag is false */
+ foreach(lc, array)
+ {
+ ObjElem *elem = (ObjElem *) lfirst(lc);
+
+ Assert(elem->objtype == ObjTypeObject ||
+ elem->objtype == ObjTypeString);
+
+ if (!elem->value.object->present &&
+ elem->objtype == ObjTypeObject)
+ array = foreach_delete_current(array, lc);
+ }
+
+ }
+
+ /* Check for empty list after removing elements */
+ if (list_length(array) == 0)
+ return;
+
+ object_name = append_object_to_format_string(tree, sub_fmt);
+
+ param = new_object(ObjTypeArray, object_name);
+ param->value.array = array;
+ append_premade_object(tree, param);
+}
+
+/*
+ * Append a boolean parameter to a tree.
+ */
+static void
+append_bool_object(ObjTree *tree, char *sub_fmt, bool value)
+{
+ ObjElem *param;
+ char *object_name = sub_fmt;
+ bool is_present_flag = false;
+
+ Assert(sub_fmt);
+
+ /*
+ * Check if the format string is 'present' and if yes, store the boolean
+ * value
+ */
+ if (strcmp(sub_fmt, "present") == 0)
+ {
+ is_present_flag = true;
+ tree->present = value;
+ }
+
+ if (!is_present_flag)
+ object_name = append_object_to_format_string(tree, sub_fmt);
+
+ param = new_object(ObjTypeBool, object_name);
+ param->value.boolean = value;
+ append_premade_object(tree, param);
+}
+
+/*
+ * Append the input format string to the ObjTree.
+ */
+static void
+append_format_string(ObjTree *tree, char *sub_fmt)
+{
+ int len;
+ char *fmt;
+
+ if (tree->fmtinfo == NULL)
+ return;
+
+ fmt = tree->fmtinfo->data;
+ len = tree->fmtinfo->len;
+
+ /* Add a separator if necessary */
+ if (len > 0 && fmt[len - 1] != ' ')
+ appendStringInfoSpaces(tree->fmtinfo, 1);
+
+ appendStringInfoString(tree->fmtinfo, sub_fmt);
+}
+
+/*
+ * Append a NULL object to a tree.
+ */
+static void
+append_null_object(ObjTree *tree, char *sub_fmt)
+{
+ char *object_name;
+
+ Assert(sub_fmt);
+
+ if (!verbose)
+ return;
+
+ object_name = append_object_to_format_string(tree, sub_fmt);
+
+ append_premade_object(tree, new_object(ObjTypeNull, object_name));
+}
+
+/*
+ * Append an object parameter to a tree.
+ */
+static void
+append_object_object(ObjTree *tree, char *sub_fmt, ObjTree *value)
+{
+ ObjElem *param;
+ char *object_name;
+
+ Assert(sub_fmt);
+
+ if (!verbose && !value->present)
+ return;
+
+ object_name = append_object_to_format_string(tree, sub_fmt);
+
+ param = new_object(ObjTypeObject, object_name);
+ param->value.object = value;
+ append_premade_object(tree, param);
+}
+
+/*
+ * Return the object name which is extracted from the input "*%{name[:.]}*"
+ * style string. And append the input format string to the ObjTree.
+ */
+static char *
+append_object_to_format_string(ObjTree *tree, char *sub_fmt)
+{
+ StringInfoData object_name;
+ const char *end_ptr, *start_ptr;
+ int length;
+ char *tmp_str;
+
+ if (sub_fmt == NULL || tree->fmtinfo == NULL)
+ return sub_fmt;
+
+ initStringInfo(&object_name);
+
+ start_ptr = strchr(sub_fmt, '{');
+ end_ptr = strchr(sub_fmt, ':');
+ if (end_ptr == NULL)
+ end_ptr = strchr(sub_fmt, '}');
+
+ if (start_ptr != NULL && end_ptr != NULL)
+ {
+ length = end_ptr - start_ptr - 1;
+ tmp_str = (char *) palloc(length + 1);
+ strncpy(tmp_str, start_ptr + 1, length);
+ tmp_str[length] = '\0';
+ appendStringInfoString(&object_name, tmp_str);
+ pfree(tmp_str);
+ }
+
+ if (object_name.len == 0)
+ elog(ERROR, "object name not found");
+
+ append_format_string(tree, sub_fmt);
+
+ return object_name.data;
+
+}
+
+/*
+ * Append a preallocated parameter to a tree.
+ */
+static inline void
+append_premade_object(ObjTree *tree, ObjElem *elem)
+{
+ slist_push_head(&tree->params, &elem->node);
+ tree->numParams++;
+}
+
+/*
+ * Append a string parameter to a tree.
+ */
+static void
+append_string_object(ObjTree *tree, char *sub_fmt, char * object_name,
+ char *value)
+{
+ ObjElem *param;
+
+ Assert(sub_fmt);
+
+ if (!verbose && (value == NULL || value[0] == '\0'))
+ return;
+
+ append_format_string(tree, sub_fmt);
+ param = new_object(ObjTypeString, object_name);
+ param->value.string = value;
+ append_premade_object(tree, param);
+}
+
+/*
+ * 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.
+ */
+static void
+format_type_detailed(Oid type_oid, int32 typemod,
+ Oid *nspid, char **typename, 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);
+
+ /*
+ * Check if it's a regular (variable length) array type. As above,
+ * fixed-length array types such as "name" shouldn't get deconstructed.
+ */
+ array_base_type = typeform->typelem;
+
+ *typearray = (IsTrueArrayType(typeform) && typeform->typstorage != TYPSTORAGE_PLAIN);
+
+ if (*typearray)
+ {
+ /* Switch our attention to the array element type */
+ 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 INTERVALOID:
+ *typename = pstrdup("INTERVAL");
+ break;
+ case TIMESTAMPTZOID:
+ if (typemod < 0)
+ *typename = pstrdup("TIMESTAMP WITH TIME ZONE");
+ else
+ /* otherwise, WITH TZ is added by typmod. */
+ *typename = pstrdup("TIMESTAMP");
+ break;
+ case TIMESTAMPOID:
+ *typename = pstrdup("TIMESTAMP");
+ break;
+ case TIMETZOID:
+ if (typemod < 0)
+ *typename = pstrdup("TIME WITH TIME ZONE");
+ else
+ /* otherwise, WITH TZ is added by typmod. */
+ *typename = pstrdup("TIME");
+ break;
+ case TIMEOID:
+ *typename = 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;
+ *typename = pstrdup(NameStr(typeform->typname));
+ }
+
+ if (typemod >= 0)
+ *typemodstr = printTypmod("", typemod, typeform->typmodout);
+ else
+ *typemodstr = pstrdup("");
+
+ ReleaseSysCache(tuple);
+}
+
+/*
+ * 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 "";
+ default:
+ elog(ERROR, "unexpected persistence marking %c", persistence);
+ return ""; /* make compiler happy */
+ }
+}
+
+/*
+ * Return the string representation of the given storagetype value.
+ */
+static inline char *
+get_type_storage(char storagetype)
+{
+ switch (storagetype)
+ {
+ case 'p':
+ return "plain";
+ case 'e':
+ return "external";
+ case 'x':
+ return "extended";
+ case 'm':
+ return "main";
+ default:
+ elog(ERROR, "invalid storage specifier %c", storagetype);
+ }
+}
+
+/*
+ * Allocate a new parameter.
+ */
+static ObjElem *
+new_object(ObjType type, char *name)
+{
+ ObjElem *param;
+
+ param = palloc0(sizeof(ObjElem));
+ param->name = name;
+ param->objtype = type;
+
+ return param;
+}
+
+/*
+ * Allocate a new object parameter.
+ */
+static ObjElem *
+new_object_object(ObjTree *value)
+{
+ ObjElem *param;
+
+ param = new_object(ObjTypeObject, NULL);
+ param->value.object = value;
+
+ return param;
+}
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(char *fmt)
+{
+ ObjTree *params;
+
+ params = palloc0(sizeof(ObjTree));
+ params->present = true;
+ slist_init(¶ms->params);
+
+ if (fmt)
+ {
+ params->fmtinfo = makeStringInfo();
+ appendStringInfoString(params->fmtinfo, fmt);
+ }
+
+ return params;
+}
+
+/*
+ * A helper routine to set up %{}D and %{}O elements.
+ *
+ * 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 ObjTree *
+new_objtree_for_qualname(Oid nspid, char *name)
+{
+ ObjTree *qualified;
+ char *namespace;
+
+ if (isAnyTempNamespace(nspid))
+ namespace = pstrdup("pg_temp");
+ else
+ namespace = get_namespace_name(nspid);
+
+ qualified = new_objtree_VA(NULL, 2,
+ "schemaname", ObjTypeString, namespace,
+ "objname", ObjTypeString, pstrdup(name));
+
+ return qualified;
+}
+
+/*
+ * A helper routine to set up %{}D and %{}O elements, with the object specified
+ * by classId/objId.
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+ ObjTree *qualified;
+ 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);
+
+ qualified = new_objtree_for_qualname(DatumGetObjectId(obj_nsp),
+ NameStr(*DatumGetName(obj_name)));
+ table_close(catalog, AccessShareLock);
+
+ return qualified;
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+ Oid typnspid;
+ char *type_nsp;
+ char *type_name = NULL;
+ char *typmodstr;
+ bool type_array;
+
+ format_type_detailed(typeId, typmod,
+ &typnspid, &type_name, &typmodstr, &type_array);
+
+ if (OidIsValid(typnspid))
+ type_nsp = get_namespace_name_or_temp(typnspid);
+ else
+ type_nsp = pstrdup("");
+
+ return new_objtree_VA(NULL, 4,
+ "schemaname", ObjTypeString, type_nsp,
+ "typename", ObjTypeString, type_name,
+ "typmod", ObjTypeString, typmodstr,
+ "typarray", ObjTypeBool, type_array);
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one, a
+ * name (string), type (from the ObjType enum) and value must be supplied. The
+ * value must match the type given; for instance, ObjTypeInteger requires an
+ * int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
+ * ObjElem), ObjTypeObject requires an ObjTree, and so on. Each element type *
+ * must match the conversion specifier given in the format string, as described
+ * in ddl_deparse_expand_command, q.v.
+ *
+ * Note we don't have the luxury of sprintf-like compiler warnings for
+ * malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+ ObjTree *tree;
+ va_list args;
+ int i;
+
+ /* Set up the toplevel object and its "fmt" */
+ tree = new_objtree(fmt);
+
+ /* And process the given varargs */
+ va_start(args, numobjs);
+ for (i = 0; i < numobjs; i++)
+ {
+ char *name;
+ ObjType type;
+ ObjElem *elem;
+
+ name = va_arg(args, char *);
+ type = va_arg(args, ObjType);
+ elem = new_object(type, NULL);
+
+ /*
+ * For all param types other than ObjTypeNull, there must be a value in
+ * the varargs. Fetch it and add the fully formed subobject into the
+ * main object.
+ */
+ switch (type)
+ {
+ case ObjTypeNull:
+ /* Null params don't have a value (obviously) */
+ break;
+ case ObjTypeBool:
+ elem->value.boolean = va_arg(args, int);
+ break;
+ case ObjTypeString:
+ elem->value.string = va_arg(args, char *);
+ break;
+ case ObjTypeArray:
+ elem->value.array = va_arg(args, List *);
+ break;
+ case ObjTypeInteger:
+ elem->value.integer = va_arg(args, int);
+ break;
+ case ObjTypeFloat:
+ elem->value.flt = va_arg(args, double);
+ break;
+ case ObjTypeObject:
+ elem->value.object = va_arg(args, ObjTree *);
+ break;
+ default:
+ elog(ERROR, "invalid ObjTree element type %d", type);
+ }
+
+ elem->name = name;
+ append_premade_object(tree, elem);
+ }
+
+ va_end(args);
+ return tree;
+}
+
+/*
+ * Process the pre-built format string from the ObjTree into the output parse
+ * state.
+ */
+static void
+objtree_fmt_to_jsonb_element(JsonbParseState *state, ObjTree *tree)
+{
+ JsonbValue key;
+ JsonbValue val;
+
+ if (tree->fmtinfo == NULL)
+ return;
+
+ /* Push the key first */
+ key.type = jbvString;
+ key.val.string.val = "fmt";
+ key.val.string.len = strlen(key.val.string.val);
+ pushJsonbValue(&state, WJB_KEY, &key);
+
+ /* Then process the pre-built format string */
+ val.type = jbvString;
+ val.val.string.len = tree->fmtinfo->len;
+ val.val.string.val = tree->fmtinfo->data;
+ pushJsonbValue(&state, WJB_VALUE, &val);
+}
+
+/*
+ * Create a JSONB representation from an ObjTree.
+ */
+static Jsonb *
+objtree_to_jsonb(ObjTree *tree)
+{
+ JsonbValue *value;
+
+ value = objtree_to_jsonb_rec(tree, NULL);
+ return JsonbValueToJsonb(value);
+}
+
+/*
+ * Helper for objtree_to_jsonb: process an individual element from an object or
+ * an array into the output parse state.
+ */
+static void
+objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
+ JsonbIteratorToken elem_token)
+{
+ JsonbValue val;
+
+ switch (object->objtype)
+ {
+ case ObjTypeNull:
+ val.type = jbvNull;
+ pushJsonbValue(&state, elem_token, &val);
+ break;
+
+ case ObjTypeString:
+ val.type = jbvString;
+ val.val.string.len = strlen(object->value.string);
+ val.val.string.val = object->value.string;
+ pushJsonbValue(&state, elem_token, &val);
+ break;
+
+ case ObjTypeInteger:
+ val.type = jbvNumeric;
+ val.val.numeric = (Numeric)
+ DatumGetNumeric(DirectFunctionCall1(int8_numeric,
+ object->value.integer));
+ pushJsonbValue(&state, elem_token, &val);
+ break;
+
+ case ObjTypeFloat:
+ val.type = jbvNumeric;
+ val.val.numeric = (Numeric)
+ DatumGetNumeric(DirectFunctionCall1(float8_numeric,
+ object->value.integer));
+ pushJsonbValue(&state, elem_token, &val);
+ break;
+
+ case ObjTypeBool:
+ val.type = jbvBool;
+ val.val.boolean = object->value.boolean;
+ pushJsonbValue(&state, elem_token, &val);
+ break;
+
+ case ObjTypeObject:
+ /* Recursively add the object into the existing parse state */
+ objtree_to_jsonb_rec(object->value.object, state);
+ break;
+
+ case ObjTypeArray:
+ {
+ ListCell *cell;
+
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+ foreach(cell, object->value.array)
+ {
+ ObjElem *elem = lfirst(cell);
+
+ objtree_to_jsonb_element(state, elem, WJB_ELEM);
+ }
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+ }
+ break;
+
+ default:
+ elog(ERROR, "unrecognized object type %d", object->objtype);
+ break;
+ }
+}
+
+/*
+ * Recursive helper for objtree_to_jsonb.
+ */
+static JsonbValue *
+objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state)
+{
+ slist_iter iter;
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ objtree_fmt_to_jsonb_element(state, tree);
+
+ slist_foreach(iter, &tree->params)
+ {
+ ObjElem *object = slist_container(ObjElem, node, iter.cur);
+ JsonbValue key;
+
+ /* Push the key first */
+ key.type = jbvString;
+ key.val.string.len = strlen(object->name);
+ key.val.string.val = object->name;
+ pushJsonbValue(&state, WJB_KEY, &key);
+
+ /* Then process the value according to its type */
+ objtree_to_jsonb_element(state, object, WJB_VALUE);
+ }
+
+ return pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Subroutine for CREATE TABLE/CREATE DOMAIN deparsing.
+ *
+ * Given a table OID or domain OID, obtain its constraints and append them to
+ * the given elements list. The updated list is returned.
+ *
+ * This works for typed tables, regular tables, and domains.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static List *
+obtainConstraints(List *elements, Oid relationId, Oid domainId,
+ ConstraintObjType objType)
+{
+ Relation conRel;
+ ScanKeyData key;
+ SysScanDesc scan;
+ HeapTuple tuple;
+ ObjTree *constr;
+ Oid relid;
+
+ /* Only one may be valid */
+ Assert(OidIsValid(relationId) ^ OidIsValid(domainId));
+
+ relid = OidIsValid(relationId) ? ConstraintRelidTypidNameIndexId :
+ ConstraintTypidIndexId;
+
+ /*
+ * 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, relid, 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;
+
+ 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_TRIGGER:
+ contype = "trigger";
+ break;
+ case CONSTRAINT_EXCLUSION:
+ contype = "exclusion";
+ break;
+ default:
+ elog(ERROR, "unrecognized constraint type");
+ }
+
+ /*
+ * "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.
+ */
+ constr = new_objtree_VA("CONSTRAINT %{name}I %{definition}s", 4,
+ "type", ObjTypeString, "constraint",
+ "contype", ObjTypeString, contype,
+ "name", ObjTypeString, NameStr(constrForm->conname),
+ "definition", ObjTypeString,
+ 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))
+ append_string_object(constr,
+ "USING INDEX TABLESPACE %{tblspc}s",
+ "tblspc",
+ get_tablespace_name(tblspc));
+ }
+
+ elements = lappend(elements, new_object_object(constr));
+ }
+
+ systable_endscan(scan);
+ table_close(conRel, AccessShareLock);
+
+ return elements;
+}
+
+/*
+ * Obtain the deparsed default value for the given column of the given table.
+ *
+ * Caller must have set a correct deparse context.
+ */
+static char *
+RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext,
+ List **exprs)
+{
+ Node *defval;
+ char *defstr;
+
+ defval = build_column_default(rel, attno);
+ defstr = deparse_expression(defval, dpcontext, false, false);
+
+ /* Collect the expression for later replication safety checks */
+ if (exprs)
+ *exprs = lappend(*exprs, defval);
+
+ return defstr;
+}
+
+/*
+ * Obtain the deparsed partition bound expression for the given table.
+ */
+static char *
+RelationGetPartitionBound(Oid relid)
+{
+ Datum deparsed;
+ Datum boundDatum;
+ bool isnull;
+ HeapTuple tuple;
+
+ tuple = SearchSysCache1(RELOID, relid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation with OID %u", relid);
+
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ deparsed = DirectFunctionCall2(pg_get_expr,
+ CStringGetTextDatum(TextDatumGetCString(boundDatum)),
+ relid);
+
+ ReleaseSysCache(tuple);
+
+ return TextDatumGetCString(deparsed);
+}
+
+/*
+ * 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 %{compression}s %{default}s %{not_null}s %{collation}s
+ */
+static ObjTree *
+deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
+ ColumnDef *coldef, bool is_alter, List **exprs)
+{
+ ObjTree *ret;
+ ObjTree *tmp_obj;
+ Oid relid = RelationGetRelid(relation);
+ HeapTuple attrTup;
+ Form_pg_attribute attrForm;
+ Oid typid;
+ int32 typmod;
+ Oid typcollation;
+ bool saw_notnull;
+ ListCell *cell;
+
+ /*
+ * Inherited columns without local definitions must not be emitted.
+ *
+ * XXX maybe it is useful to have them with "present = false" or some
+ * such?
+ */
+ if (!coldef->is_local)
+ return NULL;
+
+ 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);
+
+ ret = new_objtree_VA("%{name}I %{coltype}T", 3,
+ "type", ObjTypeString, "column",
+ "name", ObjTypeString, coldef->colname,
+ "coltype", ObjTypeObject,
+ new_objtree_for_type(typid, typmod));
+
+ if (!composite)
+ append_string_object(ret, "STORAGE %{colstorage}s", "colstorage",
+ get_type_storage(attrForm->attstorage));
+
+ /* USING clause */
+ tmp_obj = new_objtree("COMPRESSION");
+ if (coldef->compression)
+ append_string_object(tmp_obj, "%{compression_method}I",
+ "compression_method", coldef->compression);
+ else
+ {
+ append_null_object(tmp_obj, "%{compression_method}I");
+ append_not_present(tmp_obj);
+ }
+ append_object_object(ret, "%{compression}s", tmp_obj);
+
+ tmp_obj = new_objtree("COLLATE");
+ if (OidIsValid(typcollation))
+ append_object_object(tmp_obj, "%{name}D",
+ new_objtree_for_qualname_id(CollationRelationId,
+ typcollation));
+ else
+ append_not_present(tmp_obj);
+ append_object_object(ret, "%{collation}s", tmp_obj);
+
+ 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;
+ }
+ }
+
+ if (is_alter && coldef->is_not_null)
+ saw_notnull = true;
+
+ append_string_object(ret, "%{not_null}s", "not_null",
+ saw_notnull ? "NOT NULL" : "");
+
+ tmp_obj = new_objtree("DEFAULT");
+ if (attrForm->atthasdef &&
+ coldef->generated != ATTRIBUTE_GENERATED_STORED)
+ {
+ char *defstr;
+
+ defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+ dpcontext, exprs);
+
+ append_string_object(tmp_obj, "%{default}s", "default", defstr);
+ }
+ else
+ append_not_present(tmp_obj);
+ append_object_object(ret, "%{default}s", tmp_obj);
+
+ /* IDENTITY COLUMN */
+ if (coldef->identity)
+ {
+ Oid attno = get_attnum(relid, coldef->colname);
+
+ seqrelid = getIdentitySequence(relid, attno, true);
+ if (OidIsValid(seqrelid) && coldef->identitySequence)
+ seqrelid = RangeVarGetRelid(coldef->identitySequence, NoLock, false);
+ }
+
+ if (OidIsValid(seqrelid))
+ {
+ tmp_obj = deparse_ColumnIdentity(seqrelid, coldef->identity, is_alter);
+ append_object_object(ret, "%{identity_column}s", tmp_obj);
+ }
+
+ /* GENERATED COLUMN EXPRESSION */
+ tmp_obj = new_objtree("GENERATED ALWAYS AS");
+ if (coldef->generated == ATTRIBUTE_GENERATED_STORED)
+ {
+ char *defstr;
+
+ defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+ dpcontext, exprs);
+ append_string_object(tmp_obj, "(%{generation_expr}s) STORED",
+ "generation_expr", defstr);
+ }
+ else
+ append_not_present(tmp_obj);
+
+ append_object_object(ret, "%{generated_column}s", tmp_obj);
+ }
+
+ ReleaseSysCache(attrTup);
+
+ return ret;
+}
+
+/*
+ * 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 ObjTree *
+deparse_ColumnDef_typed(Relation relation, List *dpcontext, ColumnDef *coldef)
+{
+ ObjTree *ret = NULL;
+ ObjTree *tmp_obj;
+ 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);
+
+ 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 (!saw_notnull && !attrForm->atthasdef)
+ {
+ ReleaseSysCache(attrTup);
+ return NULL;
+ }
+
+ tmp_obj = new_objtree("DEFAULT");
+ if (attrForm->atthasdef)
+ {
+ char *defstr;
+
+ defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+ dpcontext, NULL);
+
+ append_string_object(tmp_obj, "%{default}s", "default", defstr);
+ }
+ else
+ append_not_present(tmp_obj);
+
+ ret = new_objtree_VA("%{name}I WITH OPTIONS %{not_null}s %{default}s", 4,
+ "type", ObjTypeString, "column",
+ "name", ObjTypeString, coldef->colname,
+ "not_null", ObjTypeString,
+ saw_notnull ? "NOT NULL" : "",
+ "default", ObjTypeObject, tmp_obj);
+
+ /* Generated columns are not supported on typed tables, so we are done */
+
+ ReleaseSysCache(attrTup);
+
+ return ret;
+}
+
+/*
+ * Deparse the definition of column identity.
+ *
+ * Verbose syntax
+ * SET GENERATED %{option}s %{identity_type}s %{seq_definition: }s
+ * OR
+ * GENERATED %{option}s AS IDENTITY %{identity_type}s ( %{seq_definition: }s )
+ */
+static ObjTree *
+deparse_ColumnIdentity(Oid seqrelid, char identity, bool alter_table)
+{
+ ObjTree *ret;
+ ObjTree *ident_obj;
+ List *elems = NIL;
+ Form_pg_sequence seqform;
+ Sequence_values *seqvalues;
+ char *identfmt;
+ char *objfmt;
+
+ if (alter_table)
+ {
+ identfmt = "SET GENERATED ";
+ objfmt = "%{option}s";
+ }
+ else
+ {
+ identfmt = "GENERATED ";
+ objfmt = "%{option}s AS IDENTITY";
+ }
+
+ ident_obj = new_objtree(identfmt);
+
+ if (identity == ATTRIBUTE_IDENTITY_ALWAYS)
+ append_string_object(ident_obj, objfmt, "option", "ALWAYS");
+ else if (identity == ATTRIBUTE_IDENTITY_BY_DEFAULT)
+ append_string_object(ident_obj, objfmt, "option", "BY DEFAULT");
+ else
+ append_not_present(ident_obj);
+
+ ret = new_objtree_VA("%{identity_type}s", 1,
+ "identity_type", ObjTypeObject, ident_obj);
+
+ seqvalues = get_sequence_values(seqrelid);
+ seqform = seqvalues->seqform;
+
+ /* Definition elements */
+ elems = lappend(elems, deparse_Seq_Cache(seqform, alter_table));
+ elems = lappend(elems, deparse_Seq_Cycle(seqform, alter_table));
+ elems = lappend(elems, deparse_Seq_IncrementBy(seqform, alter_table));
+ elems = lappend(elems, deparse_Seq_Minvalue(seqform, alter_table));
+ elems = lappend(elems, deparse_Seq_Maxvalue(seqform, alter_table));
+ elems = lappend(elems, deparse_Seq_Startwith(seqform, alter_table));
+ elems = lappend(elems, deparse_Seq_Restart(seqvalues->last_value));
+ /* We purposefully do not emit OWNED BY here */
+
+ if (alter_table)
+ append_array_object(ret, "%{seq_definition: }s", elems);
+ else
+ append_array_object(ret, "( %{seq_definition: }s )", elems);
+
+ return ret;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ *
+ * Verbose syntax
+ * ALTER COLUMN %{column}I RESET|SET (%{options:, }s)
+ */
+static ObjTree *
+deparse_ColumnSetOptions(AlterTableCmd *subcmd)
+{
+ List *sets = NIL;
+ ListCell *cell;
+ ObjTree *ret;
+ bool is_reset = subcmd->subtype == AT_ResetOptions;
+
+ ret = new_objtree_VA("ALTER COLUMN %{column}I %{option}s", 2,
+ "column", ObjTypeString, subcmd->name,
+ "option", ObjTypeString, is_reset ? "RESET" : "SET");
+
+ foreach(cell, (List *) subcmd->def)
+ {
+ DefElem *elem;
+ ObjTree *set;
+
+ elem = (DefElem *) lfirst(cell);
+ set = deparse_DefElem(elem, is_reset);
+ sets = lappend(sets, new_object_object(set));
+ }
+
+ Assert(sets);
+ append_array_object(ret, "(%{options:, }s)", sets);
+
+ return ret;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ *
+ * Verbose syntax
+ * RESET|SET (%{options:, }s)
+ */
+static ObjTree *
+deparse_RelSetOptions(AlterTableCmd *subcmd)
+{
+ List *sets = NIL;
+ ListCell *cell;
+ bool is_reset = subcmd->subtype == AT_ResetRelOptions;
+
+ foreach(cell, (List *) subcmd->def)
+ {
+ DefElem *elem;
+ ObjTree *set;
+
+ elem = (DefElem *) lfirst(cell);
+ set = deparse_DefElem(elem, is_reset);
+ sets = lappend(sets, new_object_object(set));
+ }
+
+ Assert(sets);
+
+ return new_objtree_VA("%{set_reset}s (%{options:, }s)", 2,
+ "set_reset", ObjTypeString, is_reset ? "RESET" : "SET",
+ "options", ObjTypeArray, sets);
+}
+
+/*
+ * Deparse DefElems, as used e.g. by ALTER COLUMN ... SET, into a list of SET
+ * (...) or RESET (...) contents.
+ *
+ * Verbose syntax
+ * %{label}s = %{value}L
+ */
+static ObjTree *
+deparse_DefElem(DefElem *elem, bool is_reset)
+{
+ ObjTree *ret;
+ ObjTree *optname = new_objtree("");
+
+ if (elem->defnamespace != NULL)
+ append_string_object(optname, "%{schema}I.", "schema",
+ elem->defnamespace);
+
+ append_string_object(optname, "%{label}I", "label", elem->defname);
+
+ ret = new_objtree_VA("%{label}s", 1,
+ "label", ObjTypeObject, optname);
+
+ if (!is_reset)
+ append_string_object(ret, "= %{value}L", "value",
+ elem->arg ? defGetString(elem) :
+ defGetBoolean(elem) ? "TRUE" : "FALSE");
+
+ return ret;
+}
+
+/*
+ * Deparse the INHERITS relations.
+ *
+ * Given a table OID, return a schema-qualified table list representing
+ * the parent tables.
+ */
+static List *
+deparse_InhRelations(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)))
+ {
+ ObjTree *parent;
+ Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ parent = new_objtree_for_qualname_id(RelationRelationId,
+ formInh->inhparent);
+ parents = lappend(parents, new_object_object(parent));
+ }
+
+ systable_endscan(scan);
+ table_close(inhRel, RowExclusiveLock);
+
+ return parents;
+}
+
+/*
+ * Deparse the ON COMMIT ... clause for CREATE ... TEMPORARY ...
+ *
+ * Verbose syntax
+ * ON COMMIT %{on_commit_value}s
+ */
+static ObjTree *
+deparse_OnCommitClause(OnCommitAction option)
+{
+ ObjTree *ret = new_objtree("ON COMMIT");
+ switch (option)
+ {
+ case ONCOMMIT_DROP:
+ append_string_object(ret, "%{on_commit_value}s",
+ "on_commit_value", "DROP");
+ break;
+
+ case ONCOMMIT_DELETE_ROWS:
+ append_string_object(ret, "%{on_commit_value}s",
+ "on_commit_value", "DELETE ROWS");
+ break;
+
+ case ONCOMMIT_PRESERVE_ROWS:
+ append_string_object(ret, "%{on_commit_value}s",
+ "on_commit_value", "PRESERVE ROWS");
+ break;
+
+ case ONCOMMIT_NOOP:
+ append_null_object(ret, "%{on_commit_value}s");
+ append_not_present(ret);
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Deparse the sequence CACHE option.
+ *
+ * Verbose syntax
+ * SET CACHE %{value}s
+ * OR
+ * CACHE %{value}
+ */
+static inline ObjElem *
+deparse_Seq_Cache(Form_pg_sequence seqdata, bool alter_table)
+{
+ ObjTree *ret;
+ char *fmt;
+
+ fmt = alter_table ? "SET CACHE %{value}s" : "CACHE %{value}s";
+
+ ret = new_objtree_VA(fmt, 2,
+ "clause", ObjTypeString, "cache",
+ "value", ObjTypeString,
+ psprintf(INT64_FORMAT, seqdata->seqcache));
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence CYCLE option.
+ *
+ * Verbose syntax
+ * SET %{no}s CYCLE
+ * OR
+ * %{no}s CYCLE
+ */
+static inline ObjElem *
+deparse_Seq_Cycle(Form_pg_sequence seqdata, bool alter_table)
+{
+ ObjTree *ret;
+ char *fmt;
+
+ fmt = alter_table ? "SET %{no}s CYCLE" : "%{no}s CYCLE";
+
+ ret = new_objtree_VA(fmt, 2,
+ "clause", ObjTypeString, "cycle",
+ "no", ObjTypeString,
+ seqdata->seqcycle ? "" : "NO");
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence INCREMENT BY option.
+ *
+ * Verbose syntax
+ * SET INCREMENT BY %{value}s
+ * OR
+ * INCREMENT BY %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_IncrementBy(Form_pg_sequence seqdata, bool alter_table)
+{
+ ObjTree *ret;
+ char *fmt;
+
+ fmt = alter_table ? "SET INCREMENT BY %{value}s" : "INCREMENT BY %{value}s";
+
+ ret = new_objtree_VA(fmt, 2,
+ "clause", ObjTypeString, "seqincrement",
+ "value", ObjTypeString,
+ psprintf(INT64_FORMAT, seqdata->seqincrement));
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence MAXVALUE option.
+ *
+ * Verbose syntax
+ * SET MAXVALUE %{value}s
+ * OR
+ * MAXVALUE %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_Maxvalue(Form_pg_sequence seqdata, bool alter_table)
+{
+ ObjTree *ret;
+ char *fmt;
+
+ fmt = alter_table ? "SET MAXVALUE %{value}s" : "MAXVALUE %{value}s";
+
+ ret = new_objtree_VA(fmt, 2,
+ "clause", ObjTypeString, "maxvalue",
+ "value", ObjTypeString,
+ psprintf(INT64_FORMAT, seqdata->seqmax));
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence MINVALUE option.
+ *
+ * Verbose syntax
+ * SET MINVALUE %{value}s
+ * OR
+ * MINVALUE %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_Minvalue(Form_pg_sequence seqdata, bool alter_table)
+{
+ ObjTree *ret;
+ char *fmt;
+
+ fmt = alter_table ? "SET MINVALUE %{value}s" : "MINVALUE %{value}s";
+
+ ret = new_objtree_VA(fmt, 2,
+ "clause", ObjTypeString, "minvalue",
+ "value", ObjTypeString,
+ psprintf(INT64_FORMAT, seqdata->seqmin));
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence RESTART option.
+ *
+ * Verbose syntax
+ * RESTART %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_Restart(int64 last_value)
+{
+ ObjTree *ret;
+
+ ret = new_objtree_VA("RESTART %{value}s", 2,
+ "clause", ObjTypeString, "restart",
+ "value", ObjTypeString,
+ psprintf(INT64_FORMAT, last_value));
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence AS option.
+ *
+ * Verbose syntax
+ * AS %{seqtype}T
+ */
+static inline ObjElem *
+deparse_Seq_As(Form_pg_sequence seqdata)
+{
+ ObjTree *ret;
+
+ ret = new_objtree("AS");
+ if (OidIsValid(seqdata->seqtypid))
+ append_object_object(ret, "%{seqtype}T",
+ new_objtree_for_type(seqdata->seqtypid, -1));
+ else
+ append_not_present(ret);
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the sequence START WITH option.
+ *
+ * Verbose syntax
+ * SET START WITH %{value}s
+ * OR
+ * START WITH %{value}s
+ */
+static inline ObjElem *
+deparse_Seq_Startwith(Form_pg_sequence seqdata, bool alter_table)
+{
+ ObjTree *ret;
+ char *fmt;
+
+ fmt = alter_table ? "SET START WITH %{value}s" : "START WITH %{value}s";
+
+ ret = new_objtree_VA(fmt, 2,
+ "clause", ObjTypeString, "start",
+ "value", ObjTypeString,
+ psprintf(INT64_FORMAT, seqdata->seqstart));
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the type STORAGE option.
+ *
+ * Verbose syntax
+ * STORAGE=%{value}s
+ */
+static inline ObjElem *
+deparse_Type_Storage(Form_pg_type typForm)
+{
+ ObjTree *ret;
+ ret = new_objtree_VA("STORAGE = %{value}s", 2,
+ "clause", ObjTypeString, "storage",
+ "value", ObjTypeString, get_type_storage(typForm->typstorage));
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the type RECEIVE option.
+ *
+ * Verbose syntax
+ * RECEIVE=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Receive(Form_pg_type typForm)
+{
+ ObjTree *ret;
+
+ ret = new_objtree_VA("RECEIVE=", 1,
+ "clause", ObjTypeString, "receive");
+ if (OidIsValid(typForm->typreceive))
+ append_object_object(ret, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typreceive));
+ else
+ append_not_present(ret);
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the type SEND option.
+ *
+ * Verbose syntax
+ * SEND=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Send(Form_pg_type typForm)
+{
+ ObjTree *ret;
+
+ ret = new_objtree_VA("SEND=", 1,
+ "clause", ObjTypeString, "send");
+ if (OidIsValid(typForm->typsend))
+ append_object_object(ret, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typsend));
+ else
+ append_not_present(ret);
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the type typmod_in option.
+ *
+ * Verbose syntax
+ * TYPMOD_IN=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Typmod_In(Form_pg_type typForm)
+{
+ ObjTree *ret;
+
+ ret = new_objtree_VA("TYPMOD_IN=", 1,
+ "clause", ObjTypeString, "typmod_in");
+ if (OidIsValid(typForm->typmodin))
+ append_object_object(ret, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typmodin));
+ else
+ append_not_present(ret);
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the type typmod_out option.
+ *
+ * Verbose syntax
+ * TYPMOD_OUT=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Typmod_Out(Form_pg_type typForm)
+{
+ ObjTree *ret;
+
+ ret = new_objtree_VA("TYPMOD_OUT=", 1,
+ "clause", ObjTypeString, "typmod_out");
+ if (OidIsValid(typForm->typmodout))
+ append_object_object(ret, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typmodout));
+ else
+ append_not_present(ret);
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the type analyze option.
+ *
+ * Verbose syntax
+ * ANALYZE=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Analyze(Form_pg_type typForm)
+{
+ ObjTree *ret;
+
+ ret = new_objtree_VA("ANALYZE=", 1,
+ "clause", ObjTypeString, "analyze");
+ if (OidIsValid(typForm->typanalyze))
+ append_object_object(ret, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typanalyze));
+ else
+ append_not_present(ret);
+
+ return new_object_object(ret);
+}
+
+/*
+ * Deparse the type subscript option.
+ *
+ * Verbose syntax
+ * SUBSCRIPT=%{procedure}D
+ */
+static inline ObjElem *
+deparse_Type_Subscript(Form_pg_type typForm)
+{
+ ObjTree *ret;
+
+ ret = new_objtree_VA("SUBSCRIPT=", 1,
+ "clause", ObjTypeString, "subscript");
+ if (OidIsValid(typForm->typsubscript))
+ append_object_object(ret, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typsubscript));
+ else
+ append_not_present(ret);
+
+ return new_object_object(ret);
+}
+
+/*
+ * 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 List *
+deparse_TableElements(Relation relation, List *tableElements, List *dpcontext,
+ bool typed, bool composite)
+{
+ List *elements = NIL;
+ ListCell *lc;
+
+ foreach(lc, tableElements)
+ {
+ Node *elt = (Node *) lfirst(lc);
+
+ switch (nodeTag(elt))
+ {
+ case T_ColumnDef:
+ {
+ ObjTree *tree;
+
+ tree = typed ?
+ deparse_ColumnDef_typed(relation, dpcontext,
+ (ColumnDef *) elt) :
+ deparse_ColumnDef(relation, dpcontext,
+ composite, (ColumnDef *) elt,
+ false, NULL);
+ if (tree != NULL)
+ elements = lappend(elements, new_object_object(tree));
+ }
+ break;
+ case T_Constraint:
+ break;
+ default:
+ elog(ERROR, "invalid node type %d", nodeTag(elt));
+ }
+ }
+
+ return elements;
+}
+
+/*
+ * Deparse a CreateStmt (CREATE TABLE).
+ *
+ * Given a table OID and the parse tree that created it, return an ObjTree
+ * 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_by}s %{access_method}s %{with_clause}s
+ * %{on_commit}s %{tablespace}s
+ */
+static ObjTree *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+ CreateStmt *node = (CreateStmt *) parsetree;
+ Relation relation = relation_open(objectId, AccessShareLock);
+ List *dpcontext;
+ ObjTree *ret;
+ ObjTree *tmp_obj;
+ List *list = NIL;
+ ListCell *cell;
+
+ ret = new_objtree_VA("CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D", 3,
+ "persistence", ObjTypeString,
+ get_persistence_str(relation->rd_rel->relpersistence),
+ "if_not_exists", ObjTypeString,
+ node->if_not_exists ? "IF NOT EXISTS" : "",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(relation->rd_rel->relnamespace,
+ RelationGetRelationName(relation)));
+
+ dpcontext = deparse_context_for(RelationGetRelationName(relation),
+ objectId);
+
+ /*
+ * Typed tables and partitions use a slightly different format string: we
+ * must not put table_elements with parents directly in the fmt string,
+ * because if there are no options the parentheses must not be emitted;
+ * and also, typed tables do not allow for inheritance.
+ */
+ if (node->ofTypename || node->partbound)
+ {
+ List *tableelts = NIL;
+
+ /*
+ * We can't put table elements directly in the fmt string as an array
+ * surrounded by parentheses here, because an empty clause would cause
+ * a syntax error. Therefore, we use an indirection element and set
+ * present=false when there are no elements.
+ */
+ if (node->ofTypename)
+ {
+ tmp_obj = new_objtree_for_type(relation->rd_rel->reloftype, -1);
+ append_object_object(ret, "OF %{of_type}T", tmp_obj);
+ }
+ else
+ {
+ List *parents;
+ ObjElem *elem;
+
+ parents = deparse_InhRelations(objectId);
+ elem = (ObjElem *) linitial(parents);
+
+ Assert(list_length(parents) == 1);
+
+ append_format_string(ret, "PARTITION OF");
+
+ append_object_object(ret, "%{parent_identity}D",
+ elem->value.object);
+ }
+
+ tableelts = deparse_TableElements(relation, node->tableElts, dpcontext,
+ true, /* typed table */
+ false); /* not composite */
+ tableelts = obtainConstraints(tableelts, objectId, InvalidOid,
+ ConstrObjTable);
+
+ tmp_obj = new_objtree("");
+ if (tableelts)
+ append_array_object(tmp_obj, "(%{elements:, }s)", tableelts);
+ else
+ append_not_present(tmp_obj);
+
+ append_object_object(ret, "%{table_elements}s", tmp_obj);
+ }
+ else
+ {
+ List *tableelts = NIL;
+
+ /*
+ * There is no need to process LIKE clauses separately; they have
+ * already been transformed into columns and constraints.
+ */
+
+ /*
+ * 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.
+ */
+ tableelts = deparse_TableElements(relation, node->tableElts, dpcontext,
+ false, /* not typed table */
+ false); /* not composite */
+ tableelts = obtainConstraints(tableelts, objectId, InvalidOid,
+ ConstrObjTable);
+
+ if (tableelts)
+ append_array_object(ret, "(%{table_elements:, }s)", tableelts);
+ else
+ append_format_string(ret, "()");
+
+ /*
+ * 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.
+ */
+ tmp_obj = new_objtree("INHERITS");
+ if (node->inhRelations != NIL)
+ append_array_object(tmp_obj, "(%{parents:, }D)", deparse_InhRelations(objectId));
+ else
+ {
+ append_null_object(tmp_obj, "(%{parents:, }D)");
+ append_not_present(tmp_obj);
+ }
+ append_object_object(ret, "%{inherits}s", tmp_obj);
+ }
+
+ /* FOR VALUES clause */
+ if (node->partbound)
+ {
+ /*
+ * Get pg_class.relpartbound. We cannot use partbound in the parsetree
+ * directly as it's the original partbound expression which haven't
+ * been transformed.
+ */
+ append_string_object(ret, "%{partition_bound}s", "partition_bound",
+ RelationGetPartitionBound(objectId));
+ }
+
+ /* PARTITION BY clause */
+ tmp_obj = new_objtree("PARTITION BY");
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ append_string_object(tmp_obj, "%{definition}s", "definition",
+ pg_get_partkeydef_string(objectId));
+ else
+ {
+ append_null_object(tmp_obj, "%{definition}s");
+ append_not_present(tmp_obj);
+ }
+ append_object_object(ret, "%{partition_by}s", tmp_obj);
+
+ /* USING clause */
+ tmp_obj = new_objtree("USING");
+ if (node->accessMethod)
+ append_string_object(tmp_obj, "%{access_method}I", "access_method",
+ node->accessMethod);
+ else
+ {
+ append_null_object(tmp_obj, "%{access_method}I");
+ append_not_present(tmp_obj);
+ }
+ append_object_object(ret, "%{access_method}s", tmp_obj);
+
+ /* WITH clause */
+ tmp_obj = new_objtree("WITH");
+
+ foreach(cell, node->options)
+ {
+ ObjTree *tmp_obj2;
+ DefElem *opt = (DefElem *) lfirst(cell);
+
+ tmp_obj2 = deparse_DefElem(opt, false);
+ list = lappend(list, new_object_object(tmp_obj2));
+ }
+
+ if (list)
+ append_array_object(tmp_obj, "(%{with:, }s)", list);
+ else
+ append_not_present(tmp_obj);
+
+ append_object_object(ret, "%{with_clause}s", tmp_obj);
+
+ append_object_object(ret, "%{on_commit}s",
+ deparse_OnCommitClause(node->oncommit));
+
+ tmp_obj = new_objtree("TABLESPACE");
+ if (node->tablespacename)
+ append_string_object(tmp_obj, "%{tablespace}I", "tablespace",
+ node->tablespacename);
+ else
+ {
+ append_null_object(tmp_obj, "%{tablespace}I");
+ append_not_present(tmp_obj);
+ }
+ append_object_object(ret, "%{tablespace}s", tmp_obj);
+
+ relation_close(relation, AccessShareLock);
+
+ return ret;
+}
+
+/*
+ * Deparse CREATE TABLE AS command.
+ *
+ * deparse_CreateStmt do the actual work as we deparse the final CreateStmt for
+ * CREATE TABLE AS command.
+ */
+static ObjTree *
+deparse_CreateTableAsStmt(CollectedCommand *cmd)
+{
+ Oid objectId;
+ Node *parsetree;
+
+ Assert(cmd->type == SCT_CreateTableAs);
+
+ parsetree = cmd->d.ctas.real_create;
+ objectId = cmd->d.ctas.address.objectId;
+
+ return deparse_CreateStmt(objectId, parsetree);
+}
+
+/*
+ * Deparse all the collected subcommands and return an ObjTree representing the
+ * alter command.
+ *
+ * Verbose syntax
+ * ALTER reltype %{only}s %{identity}D %{subcmds:, }s
+ */
+static ObjTree *
+deparse_AlterRelation(CollectedCommand *cmd)
+{
+ ObjTree *ret;
+ ObjTree *tmp_obj;
+ ObjTree *tmp_obj2;
+ List *dpcontext;
+ Relation rel;
+ List *subcmds = NIL;
+ ListCell *cell;
+ const char *reltype;
+ bool istype = false;
+ List *exprs = NIL;
+ Oid relId = cmd->d.alterTable.objectId;
+ AlterTableStmt *stmt = NULL;
+
+ Assert(cmd->type == SCT_AlterTable);
+ stmt = (AlterTableStmt *) cmd->parsetree;
+ Assert(IsA(stmt, AlterTableStmt));
+
+ /*
+ * ALTER TABLE subcommands generated for TableLikeClause is processed in
+ * the top level CREATE TABLE command; return empty here.
+ */
+ if (stmt->table_like)
+ return NULL;
+
+ rel = relation_open(relId, AccessShareLock);
+ dpcontext = deparse_context_for(RelationGetRelationName(rel),
+ relId);
+
+ switch (rel->rd_rel->relkind)
+ {
+ case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
+ reltype = "TABLE";
+ break;
+ case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
+ reltype = "INDEX";
+ break;
+ case RELKIND_VIEW:
+ reltype = "VIEW";
+ break;
+ case RELKIND_COMPOSITE_TYPE:
+ reltype = "TYPE";
+ istype = true;
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ reltype = "FOREIGN TABLE";
+ break;
+ case RELKIND_MATVIEW:
+ reltype = "MATERIALIZED VIEW";
+ break;
+
+ /* TODO support for partitioned table */
+
+ default:
+ elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
+ }
+
+ ret = new_objtree_VA("ALTER %{objtype}s %{identity}D", 2,
+ "objtype", ObjTypeString, reltype,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(rel->rd_rel->relnamespace,
+ RelationGetRelationName(rel)));
+
+ foreach(cell, cmd->d.alterTable.subcmds)
+ {
+ CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
+ AlterTableCmd *subcmd = (AlterTableCmd *) sub->parsetree;
+ ObjTree *tree;
+
+ Assert(IsA(subcmd, AlterTableCmd));
+
+ /*
+ * If the ALTER TABLE command for the parent table includes subcommands
+ * for child table(s), do not deparse the subcommand for child
+ * table(s).
+ */
+ if (sub->address.objectId != relId &&
+ has_superclass(sub->address.objectId))
+ continue;
+
+ switch (subcmd->subtype)
+ {
+ case AT_AddColumn:
+ /* XXX need to set the "recurse" bit somewhere? */
+ Assert(IsA(subcmd->def, ColumnDef));
+ tree = deparse_ColumnDef(rel, dpcontext, false,
+ (ColumnDef *) subcmd->def, true, &exprs);
+ tmp_obj = new_objtree_VA("ADD %{objtype}s %{if_not_exists}s %{definition}s", 4,
+ "objtype", ObjTypeString,
+ istype ? "ATTRIBUTE" : "COLUMN",
+ "type", ObjTypeString, "add column",
+ "if_not_exists", ObjTypeString,
+ subcmd->missing_ok ? "IF NOT EXISTS" : "",
+ "definition", ObjTypeObject, tree);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_AddIndexConstraint:
+ {
+ IndexStmt *istmt;
+ Relation idx;
+ Oid constrOid = sub->address.objectId;
+
+ Assert(IsA(subcmd->def, IndexStmt));
+ istmt = (IndexStmt *) subcmd->def;
+
+ Assert(istmt->isconstraint && istmt->unique);
+
+ idx = relation_open(istmt->indexOid, AccessShareLock);
+
+ /*
+ * Verbose syntax
+ *
+ * ADD CONSTRAINT %{name}I %{constraint_type}s USING INDEX
+ * %index_name}I %{deferrable}s %{init_deferred}s
+ */
+ tmp_obj = new_objtree_VA("ADD CONSTRAINT %{name}I %{constraint_type}s USING INDEX %{index_name}I %{deferrable}s %{init_deferred}s", 6,
+ "type", ObjTypeString, "add constraint using index",
+ "name", ObjTypeString, get_constraint_name(constrOid),
+ "constraint_type", ObjTypeString,
+ istmt->primary ? "PRIMARY KEY" : "UNIQUE",
+ "index_name", ObjTypeString,
+ RelationGetRelationName(idx),
+ "deferrable", ObjTypeString,
+ istmt->deferrable ? "DEFERRABLE" : "NOT DEFERRABLE",
+ "init_deferred", ObjTypeString,
+ istmt->initdeferred ? "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE");
+
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+
+ 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_CookedColumnDefault:
+ {
+ Relation attrrel;
+ HeapTuple atttup;
+ Form_pg_attribute attStruct;
+
+ attrrel = table_open(AttributeRelationId, RowExclusiveLock);
+ atttup = SearchSysCacheCopy2(ATTNUM,
+ ObjectIdGetDatum(RelationGetRelid(rel)),
+ Int16GetDatum(subcmd->num));
+ if (!HeapTupleIsValid(atttup))
+ elog(ERROR, "cache lookup failed for attribute %d of relation with OID %u",
+ subcmd->num, RelationGetRelid(rel));
+ attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
+
+ /*
+ * Both default and generation expression not supported
+ * together.
+ */
+ if (!attStruct->attgenerated)
+ elog(WARNING, "unsupported alter table subtype %d",
+ subcmd->subtype);
+
+ heap_freetuple(atttup);
+ table_close(attrrel, RowExclusiveLock);
+ break;
+ }
+
+ case AT_AddColumnToView:
+ /* CREATE OR REPLACE VIEW -- nothing to do here */
+ break;
+
+ case AT_ColumnDefault:
+ if (subcmd->def == NULL)
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP DEFAULT", 2,
+ "type", ObjTypeString, "drop default",
+ "column", ObjTypeString, subcmd->name);
+ else
+ {
+ List *dpcontext_rel;
+ HeapTuple attrtup;
+ AttrNumber attno;
+
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET DEFAULT", 2,
+ "type", ObjTypeString, "set default",
+ "column", ObjTypeString, subcmd->name);
+
+ dpcontext_rel = deparse_context_for(RelationGetRelationName(rel),
+ RelationGetRelid(rel));
+ attrtup = SearchSysCacheAttName(RelationGetRelid(rel), subcmd->name);
+ attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
+ append_string_object(tmp_obj, "%{definition}s", "definition",
+ RelationGetColumnDefault(rel, attno,
+ dpcontext_rel,
+ NULL));
+ ReleaseSysCache(attrtup);
+ }
+
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DropNotNull:
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP NOT NULL", 2,
+ "type", ObjTypeString, "drop not null",
+ "column", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_ForceRowSecurity:
+ tmp_obj = new_objtree("FORCE ROW LEVEL SECURITY");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_NoForceRowSecurity:
+ tmp_obj = new_objtree("NO FORCE ROW LEVEL SECURITY");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_SetNotNull:
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET NOT NULL", 2,
+ "type", ObjTypeString, "set not null",
+ "column", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DropExpression:
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP EXPRESSION %{if_exists}s", 3,
+ "type", ObjTypeString, "drop expression",
+ "column", ObjTypeString, subcmd->name,
+ "if_exists", ObjTypeString,
+ subcmd->missing_ok ? "IF EXISTS" : "");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_SetStatistics:
+ {
+ Assert(IsA(subcmd->def, Integer));
+ if (subcmd->name)
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n", 3,
+ "type", ObjTypeString, "set statistics",
+ "column", ObjTypeString, subcmd->name,
+ "statistics", ObjTypeInteger,
+ intVal((Integer *) subcmd->def));
+ else
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}n SET STATISTICS %{statistics}n", 3,
+ "type", ObjTypeString, "set statistics",
+ "column", ObjTypeInteger, subcmd->num,
+ "statistics", ObjTypeInteger,
+ intVal((Integer *) subcmd->def));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ }
+ break;
+
+ case AT_SetOptions:
+ case AT_ResetOptions:
+ subcmds = lappend(subcmds, new_object_object(
+ deparse_ColumnSetOptions(subcmd)));
+ break;
+
+ case AT_SetStorage:
+ Assert(IsA(subcmd->def, String));
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s", 3,
+ "type", ObjTypeString, "set storage",
+ "column", ObjTypeString, subcmd->name,
+ "storage", ObjTypeString,
+ strVal((String *) subcmd->def));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_SetCompression:
+ Assert(IsA(subcmd->def, String));
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET COMPRESSION %{compression_method}s", 3,
+ "type", ObjTypeString, "set compression",
+ "column", ObjTypeString, subcmd->name,
+ "compression_method", ObjTypeString,
+ strVal((String *) subcmd->def));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DropColumn:
+ tmp_obj = new_objtree_VA("DROP %{objtype}s %{if_exists}s %{column}I", 4,
+ "objtype", ObjTypeString,
+ istype ? "ATTRIBUTE" : "COLUMN",
+ "type", ObjTypeString, "drop column",
+ "if_exists", ObjTypeString,
+ subcmd->missing_ok ? "IF EXISTS" : "",
+ "column", ObjTypeString, subcmd->name);
+ tmp_obj2 = new_objtree_VA("CASCADE", 1,
+ "present", ObjTypeBool, subcmd->behavior);
+ append_object_object(tmp_obj, "%{cascade}s", tmp_obj2);
+
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_AddIndex:
+ {
+ Oid idxOid = sub->address.objectId;
+ IndexStmt *istmt;
+ Relation idx;
+ const char *idxname;
+ Oid constrOid;
+
+ Assert(IsA(subcmd->def, IndexStmt));
+ istmt = (IndexStmt *) subcmd->def;
+
+ if (!istmt->isconstraint)
+ break;
+
+ idx = relation_open(idxOid, AccessShareLock);
+ idxname = RelationGetRelationName(idx);
+
+ constrOid = get_relation_constraint_oid(
+ cmd->d.alterTable.objectId, idxname, false);
+
+ tmp_obj = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", 3,
+ "type", ObjTypeString, "add constraint",
+ "name", ObjTypeString, idxname,
+ "definition", ObjTypeString,
+ pg_get_constraintdef_string(constrOid));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+
+ relation_close(idx, AccessShareLock);
+ }
+ break;
+
+ case AT_AddConstraint:
+ {
+ /* XXX need to set the "recurse" bit somewhere? */
+ Oid constrOid = sub->address.objectId;
+ bool isnull;
+ HeapTuple tup;
+ Datum val;
+ Constraint *constr;
+
+ /* Skip adding constraint for inherits table sub command */
+ if (!constrOid)
+ continue;
+
+ Assert(IsA(subcmd->def, Constraint));
+ constr = castNode(Constraint, subcmd->def);
+
+ if (!constr->skip_validation)
+ {
+ tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
+
+ if (HeapTupleIsValid(tup))
+ {
+ char *conbin;
+
+ /* Fetch constraint expression in parsetree form */
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conbin, &isnull);
+
+ if (!isnull)
+ {
+ conbin = TextDatumGetCString(val);
+ exprs = lappend(exprs, stringToNode(conbin));
+ }
+
+ ReleaseSysCache(tup);
+ }
+ }
+
+ tmp_obj = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", 3,
+ "type", ObjTypeString, "add constraint",
+ "name", ObjTypeString, get_constraint_name(constrOid),
+ "definition", ObjTypeString,
+ pg_get_constraintdef_string(constrOid));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ }
+ break;
+
+ case AT_AlterConstraint:
+ {
+ Oid constrOid = sub->address.objectId;
+ Constraint *c = (Constraint *) subcmd->def;
+
+ /* If no constraint was altered, silently skip it */
+ if (!OidIsValid(constrOid))
+ break;
+
+ Assert(IsA(c, Constraint));
+ tmp_obj = new_objtree_VA("ALTER CONSTRAINT %{name}I %{deferrable}s %{init_deferred}s", 4,
+ "type", ObjTypeString, "alter constraint",
+ "name", ObjTypeString, get_constraint_name(constrOid),
+ "deferrable", ObjTypeString,
+ c->deferrable ? "DEFERRABLE" : "NOT DEFERRABLE",
+ "init_deferred", ObjTypeString,
+ c->initdeferred ? "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE");
+
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ }
+ break;
+
+ case AT_ValidateConstraint:
+ tmp_obj = new_objtree_VA("VALIDATE CONSTRAINT %{constraint}I", 2,
+ "type", ObjTypeString, "validate constraint",
+ "constraint", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DropConstraint:
+ tmp_obj = new_objtree_VA("DROP CONSTRAINT %{if_exists}s %{constraint}I %{cascade}s", 4,
+ "type", ObjTypeString, "drop constraint",
+ "if_exists", ObjTypeString,
+ subcmd->missing_ok ? "IF EXISTS" : "",
+ "constraint", ObjTypeString, subcmd->name,
+ "cascade", ObjTypeString,
+ subcmd->behavior == DROP_CASCADE ? "CASCADE" : "");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_AlterColumnType:
+ {
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ Form_pg_attribute att;
+ ColumnDef *def;
+
+ att = &(tupdesc->attrs[sub->address.objectSubId - 1]);
+ def = (ColumnDef *) subcmd->def;
+ Assert(IsA(def, ColumnDef));
+
+ /*
+ * Verbose syntax
+ *
+ * Composite types: ALTER reltype %{column}I SET DATA TYPE
+ * %{datatype}T %{collation}s ATTRIBUTE %{cascade}s
+ *
+ * Normal types: ALTER reltype %{column}I SET DATA TYPE
+ * %{datatype}T %{collation}s COLUMN %{using}s
+ */
+ tmp_obj = new_objtree_VA("ALTER %{objtype}s %{column}I SET DATA TYPE %{datatype}T", 4,
+ "objtype", ObjTypeString,
+ istype ? "ATTRIBUTE" : "COLUMN",
+ "type", ObjTypeString, "alter column type",
+ "column", ObjTypeString, subcmd->name,
+ "datatype", ObjTypeObject,
+ new_objtree_for_type(att->atttypid,
+ att->atttypmod));
+
+ /* Add a COLLATE clause, if needed */
+ tmp_obj2 = new_objtree("COLLATE");
+ if (OidIsValid(att->attcollation))
+ {
+ ObjTree *collname;
+
+ collname = new_objtree_for_qualname_id(CollationRelationId,
+ att->attcollation);
+ append_object_object(tmp_obj2, "%{name}D", collname);
+ }
+ else
+ append_not_present(tmp_obj2);
+ append_object_object(tmp_obj, "%{collation}s", tmp_obj2);
+
+ /* If not a composite type, add the USING clause */
+ if (!istype)
+ {
+ /*
+ * If there's a USING clause, transformAlterTableStmt
+ * ran it through transformExpr and stored the
+ * resulting node in cooked_default, which we can use
+ * here.
+ */
+ tmp_obj2 = new_objtree("USING");
+ if (def->raw_default)
+ append_string_object(tmp_obj2, "%{expression}s",
+ "expression",
+ sub->usingexpr);
+ else
+ append_not_present(tmp_obj2);
+ append_object_object(tmp_obj, "%{using}s", tmp_obj2);
+ }
+
+ /* If it's a composite type, add the CASCADE clause */
+ if (istype)
+ {
+ tmp_obj2 = new_objtree("CASCADE");
+ if (subcmd->behavior != DROP_CASCADE)
+ append_not_present(tmp_obj2);
+ append_object_object(tmp_obj, "%{cascade}s", tmp_obj2);
+ }
+
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ }
+ break;
+
+#ifdef TODOLIST
+ case AT_AlterColumnGenericOptions:
+ tmp_obj = deparse_FdwOptions((List *) subcmd->def,
+ subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+#endif
+ case AT_ChangeOwner:
+ tmp_obj = new_objtree_VA("OWNER TO %{owner}I", 2,
+ "type", ObjTypeString, "change owner",
+ "owner", ObjTypeString,
+ get_rolespec_name(subcmd->newowner));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_ClusterOn:
+ tmp_obj = new_objtree_VA("CLUSTER ON %{index}I", 2,
+ "type", ObjTypeString, "cluster on",
+ "index", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DropCluster:
+ tmp_obj = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+ "type", ObjTypeString, "set without cluster");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_SetLogged:
+ tmp_obj = new_objtree_VA("SET LOGGED", 1,
+ "type", ObjTypeString, "set logged");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_SetUnLogged:
+ tmp_obj = new_objtree_VA("SET UNLOGGED", 1,
+ "type", ObjTypeString, "set unlogged");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DropOids:
+ tmp_obj = new_objtree_VA("SET WITHOUT OIDS", 1,
+ "type", ObjTypeString, "set without oids");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+ case AT_SetAccessMethod:
+ tmp_obj = new_objtree_VA("SET ACCESS METHOD %{access_method}I", 2,
+ "type", ObjTypeString, "set access method",
+ "access_method", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+ case AT_SetTableSpace:
+ tmp_obj = new_objtree_VA("SET TABLESPACE %{tablespace}I", 2,
+ "type", ObjTypeString, "set tablespace",
+ "tablespace", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_SetRelOptions:
+ case AT_ResetRelOptions:
+ subcmds = lappend(subcmds, new_object_object(
+ deparse_RelSetOptions(subcmd)));
+ break;
+
+ case AT_EnableTrig:
+ tmp_obj = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2,
+ "type", ObjTypeString, "enable trigger",
+ "trigger", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_EnableAlwaysTrig:
+ tmp_obj = new_objtree_VA("ENABLE ALWAYS TRIGGER %{trigger}I", 2,
+ "type", ObjTypeString, "enable always trigger",
+ "trigger", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_EnableReplicaTrig:
+ tmp_obj = new_objtree_VA("ENABLE REPLICA TRIGGER %{trigger}I", 2,
+ "type", ObjTypeString, "enable replica trigger",
+ "trigger", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DisableTrig:
+ tmp_obj = new_objtree_VA("DISABLE TRIGGER %{trigger}I", 2,
+ "type", ObjTypeString, "disable trigger",
+ "trigger", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_EnableTrigAll:
+ tmp_obj = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+ "type", ObjTypeString, "enable trigger all");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DisableTrigAll:
+ tmp_obj = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+ "type", ObjTypeString, "disable trigger all");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_EnableTrigUser:
+ tmp_obj = new_objtree_VA("ENABLE TRIGGER USER", 1,
+ "type", ObjTypeString, "enable trigger user");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DisableTrigUser:
+ tmp_obj = new_objtree_VA("DISABLE TRIGGER USER", 1,
+ "type", ObjTypeString, "disable trigger user");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_EnableRule:
+ tmp_obj = new_objtree_VA("ENABLE RULE %{rule}I", 2,
+ "type", ObjTypeString, "enable rule",
+ "rule", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_EnableAlwaysRule:
+ tmp_obj = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
+ "type", ObjTypeString, "enable always rule",
+ "rule", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_EnableReplicaRule:
+ tmp_obj = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
+ "type", ObjTypeString, "enable replica rule",
+ "rule", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DisableRule:
+ tmp_obj = new_objtree_VA("DISABLE RULE %{rule}I", 2,
+ "type", ObjTypeString, "disable rule",
+ "rule", ObjTypeString, subcmd->name);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_AddInherit:
+ tmp_obj = new_objtree_VA("INHERIT %{parent}D", 2,
+ "type", ObjTypeString, "inherit",
+ "parent", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ sub->address.objectId));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DropInherit:
+ tmp_obj = new_objtree_VA("NO INHERIT %{parent}D", 2,
+ "type", ObjTypeString, "drop inherit",
+ "parent", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ sub->address.objectId));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_AddOf:
+ tmp_obj = new_objtree_VA("OF %{type_of}T", 2,
+ "type", ObjTypeString, "add of",
+ "type_of", ObjTypeObject,
+ new_objtree_for_type(sub->address.objectId, -1));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DropOf:
+ tmp_obj = new_objtree_VA("NOT OF", 1,
+ "type", ObjTypeString, "not of");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_ReplicaIdentity:
+ tmp_obj = new_objtree_VA("REPLICA IDENTITY", 1,
+ "type", ObjTypeString, "replica identity");
+ switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type)
+ {
+ case REPLICA_IDENTITY_DEFAULT:
+ append_string_object(tmp_obj, "%{ident}s", "ident",
+ "DEFAULT");
+ break;
+ case REPLICA_IDENTITY_FULL:
+ append_string_object(tmp_obj, "%{ident}s", "ident",
+ "FULL");
+ break;
+ case REPLICA_IDENTITY_NOTHING:
+ append_string_object(tmp_obj, "%{ident}s", "ident",
+ "NOTHING");
+ break;
+ case REPLICA_IDENTITY_INDEX:
+ tmp_obj2 = new_objtree_VA("USING INDEX %{index}I", 1,
+ "index", ObjTypeString,
+ ((ReplicaIdentityStmt *) subcmd->def)->name);
+ append_object_object(tmp_obj, "%{ident}s", tmp_obj2);
+ break;
+ }
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_EnableRowSecurity:
+ tmp_obj = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
+ "type", ObjTypeString, "enable row security");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+
+ case AT_DisableRowSecurity:
+ tmp_obj = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
+ "type", ObjTypeString, "disable row security");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+#ifdef TODOLIST
+ case AT_GenericOptions:
+ tmp_obj = deparse_FdwOptions((List *) subcmd->def, NULL);
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+#endif
+ case AT_AttachPartition:
+ tmp_obj = new_objtree_VA("ATTACH PARTITION %{partition_identity}D", 2,
+ "type", ObjTypeString, "attach partition",
+ "partition_identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ sub->address.objectId));
+
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ append_string_object(tmp_obj, "%{partition_bound}s",
+ "partition_bound",
+ RelationGetPartitionBound(sub->address.objectId));
+
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+ case AT_DetachPartition:
+ {
+ PartitionCmd *cmd;
+
+ Assert(IsA(subcmd->def, PartitionCmd));
+ cmd = (PartitionCmd *) subcmd->def;
+
+ tmp_obj = new_objtree_VA("DETACH PARTITION %{partition_identity}D %{concurrent}s", 3,
+ "type", ObjTypeString,
+ "detach partition",
+ "partition_identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ sub->address.objectId),
+ cmd->concurrent ? "CONCURRENTLY" : "");
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+ }
+ case AT_DetachPartitionFinalize:
+ tmp_obj = new_objtree_VA("DETACH PARTITION %{partition_identity}D FINALIZE", 2,
+ "type", ObjTypeString, "detach partition finalize",
+ "partition_identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ sub->address.objectId));
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+ case AT_AddIdentity:
+ {
+ AttrNumber attnum;
+ Oid seq_relid;
+ ObjTree *seqdef;
+ ColumnDef *coldef = (ColumnDef *) subcmd->def;
+
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I", 2,
+ "type", ObjTypeString, "add identity",
+ "column", ObjTypeString, subcmd->name);
+
+ attnum = get_attnum(RelationGetRelid(rel), subcmd->name);
+ seq_relid = getIdentitySequence(RelationGetRelid(rel), attnum, true);
+
+ if (OidIsValid(seq_relid))
+ {
+ seqdef = deparse_ColumnIdentity(seq_relid, coldef->identity, false);
+ append_object_object(tmp_obj, "ADD %{identity_column}s", seqdef);
+ }
+
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ }
+ break;
+ case AT_SetIdentity:
+ {
+ DefElem *defel;
+ char identity = 0;
+ ObjTree *seqdef;
+ AttrNumber attnum;
+ Oid seq_relid;
+
+
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I", 2,
+ "type", ObjTypeString, "set identity",
+ "column", ObjTypeString, 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))
+ {
+ seqdef = deparse_ColumnIdentity(seq_relid, identity, true);
+ append_object_object(tmp_obj, "%{definition}s", seqdef);
+ }
+
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+ }
+ case AT_DropIdentity:
+ tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP IDENTITY", 2,
+ "type", ObjTypeString, "drop identity",
+ "column", ObjTypeString, subcmd->name);
+
+ append_string_object(tmp_obj, "%{if_exists}s",
+ "if_exists",
+ subcmd->missing_ok ? "IF EXISTS" : "");
+
+ subcmds = lappend(subcmds, new_object_object(tmp_obj));
+ break;
+ default:
+ elog(WARNING, "unsupported alter table subtype %d",
+ subcmd->subtype);
+ break;
+ }
+
+ /*
+ * We don't support replicating ALTER TABLE which contains volatile
+ * functions because It's possible the functions contain DDL/DML in
+ * which case these operations will be executed twice and cause
+ * duplicate data. In addition, we don't know whether the tables being
+ * accessed by these DDL/DML are published or not. So blindly allowing
+ * such functions can allow unintended clauses like the tables
+ * accessed in those functions may not even exist on the subscriber.
+ */
+ if (contain_volatile_functions((Node *) exprs))
+ elog(ERROR, "ALTER TABLE command using volatile function cannot be replicated");
+
+ /*
+ * Clean the list as we already confirmed there is no volatile
+ * function.
+ */
+ list_free(exprs);
+ exprs = NIL;
+ }
+
+ table_close(rel, AccessShareLock);
+
+ if (list_length(subcmds) == 0)
+ return NULL;
+
+ append_array_object(ret, "%{subcmds:, }s", subcmds);
+
+ return ret;
+}
+
+/*
+ * Handle deparsing of DROP commands.
+ *
+ * Verbose syntax
+ * DROP %{objtype}s IF EXISTS %%{objidentity}s %{cascade}s
+ */
+char *
+deparse_drop_command(const char *objidentity, const char *objecttype,
+ DropBehavior behavior)
+{
+ StringInfoData str;
+ char *command;
+ char *identity = (char *) objidentity;
+ ObjTree *stmt;
+ ObjTree *tmp_obj;
+ Jsonb *jsonb;
+
+ initStringInfo(&str);
+
+ stmt = new_objtree_VA("DROP %{objtype}s IF EXISTS %{objidentity}s", 2,
+ "objtype", ObjTypeString, objecttype,
+ "objidentity", ObjTypeString, identity);
+
+ tmp_obj = new_objtree_VA("CASCADE", 1,
+ "present", ObjTypeBool, behavior == DROP_CASCADE);
+ append_object_object(stmt, "%{cascade}s", tmp_obj);
+
+ jsonb = objtree_to_jsonb(stmt);
+ command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+
+ return command;
+}
+
+/*
+ * Deparse an AlterObjectSchemaStmt (ALTER ... SET SCHEMA command)
+ *
+ * Given the object address and the parse tree that created it, return an
+ * ObjTree representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{identity}s SET SCHEMA %{newschema}I
+ */
+static ObjTree *
+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;
+
+ /*
+ * 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)));
+
+ return new_objtree_VA("ALTER %{objtype}s %{identity}s SET SCHEMA %{newschema}I", 3,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->objectType, false),
+ "identity", ObjTypeString, ident,
+ "newschema", ObjTypeString, new_schema);
+}
+
+/*
+ * Deparse an AlterOwnerStmt (ALTER ... OWNER TO ...).
+ *
+ * Given the object address and the parse tree that created it, return an
+ * ObjTree representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I
+ */
+static ObjTree *
+deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree)
+{
+ AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+
+ return new_objtree_VA("ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I", 3,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->objectType, false),
+ "identity", ObjTypeString,
+ getObjectIdentity(&address, false),
+ "newowner", ObjTypeString,
+ get_rolespec_name(node->newowner));
+}
+
+/*
+ * Deparse a RenameStmt.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{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 ObjTree *
+deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+{
+ RenameStmt *node = (RenameStmt *) parsetree;
+ ObjTree *ret;
+ Relation relation;
+ Oid schemaId;
+
+ /*
+ * 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);
+ ret = new_objtree_VA("ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I", 4,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->renameType, false),
+ "if_exists", ObjTypeString,
+ node->missing_ok ? "IF EXISTS" : "",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(schemaId,
+ node->relation->relname),
+ "newname", ObjTypeString,
+ node->newname);
+ 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);
+
+ ret = new_objtree_VA("ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I", 4,
+ "only", ObjTypeString,
+ node->relation->inh ? "" : "ONLY",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ constform->conrelid),
+ "oldname", ObjTypeString, node->subname,
+ "newname", ObjTypeString, node->newname);
+ ReleaseSysCache(constrtup);
+ }
+ break;
+
+ case OBJECT_COLUMN:
+ relation = relation_open(address.objectId, AccessShareLock);
+ schemaId = RelationGetNamespace(relation);
+
+ ret = new_objtree_VA("ALTER %{objtype}s", 1,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->relationType, false));
+
+ /* Composite types do not support IF EXISTS */
+ if (node->renameType == OBJECT_COLUMN)
+ append_string_object(ret, "%{if_exists}s",
+ "if_exists",
+ node->missing_ok ? "IF EXISTS" : "");
+ if (!node->relation->inh)
+ append_string_object(ret, "%{only}s",
+ "only",
+ "ONLY");
+ append_object_object(ret, "%{identity}D",
+ new_objtree_for_qualname(schemaId,
+ node->relation->relname));
+ append_string_object(ret, "RENAME COLUMN %{colname}I",
+ "colname", node->subname);
+
+ append_string_object(ret, "TO %{newname}I", "newname",
+ node->newname);
+
+ if (node->renameType == OBJECT_ATTRIBUTE)
+ append_object_object(ret, "%{cascade}s",
+ new_objtree_VA("CASCADE", 1,
+ "present", ObjTypeBool,
+ node->behavior == DROP_CASCADE));
+
+ relation_close(relation, AccessShareLock);
+ break;
+
+ default:
+ elog(ERROR, "unsupported object type %d", node->renameType);
+ }
+
+ return ret;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function should cover all cases handled in ProcessUtilitySlow.
+ */
+static ObjTree *
+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_AlterObjectSchemaStmt:
+ return deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
+ parsetree,
+ cmd->d.simple.secondaryObject);
+
+ case T_AlterOwnerStmt:
+ return deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree);
+
+ 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",
+ (int) nodeTag(parsetree));
+ }
+
+ return NULL;
+}
+
+/*
+ * Workhorse to deparse a CollectedCommand.
+ */
+char *
+deparse_utility_command(CollectedCommand *cmd, bool verbose_mode)
+{
+ OverrideSearchPath *overridePath;
+ MemoryContext oldcxt;
+ MemoryContext tmpcxt;
+ ObjTree *tree;
+ char *command = NULL;
+ StringInfoData str;
+
+ /*
+ * 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 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);
+
+ verbose = verbose_mode;
+
+ switch (cmd->type)
+ {
+ case SCT_Simple:
+ tree = deparse_simple_command(cmd);
+ break;
+ case SCT_AlterTable:
+ tree = deparse_AlterRelation(cmd);
+ break;
+ case SCT_CreateTableAs:
+ tree = deparse_CreateTableAsStmt(cmd);
+ break;
+ default:
+ elog(ERROR, "unexpected deparse node type %d", cmd->type);
+ }
+
+ PopOverrideSearchPath();
+
+ if (tree)
+ {
+ Jsonb *jsonb;
+
+ jsonb = objtree_to_jsonb(tree);
+ 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, true);
+
+ if (command)
+ PG_RETURN_TEXT_P(cstring_to_text(command));
+
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/commands/ddl_json.c b/src/backend/commands/ddl_json.c
new file mode 100644
index 0000000000..3a57d2697c
--- /dev/null
+++ b/src/backend/commands/ddl_json.c
@@ -0,0 +1,780 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_json.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
+ *
+ * IDENTIFICATION
+ * src/backend/commands/ddl_json.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+#include "tcop/ddl_deparse.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+
+/*
+ * Conversion specifier which determines how to expand the JSON element
+ * into a string.
+ */
+typedef enum
+{
+ SpecDottedName,
+ SpecIdentifier,
+ SpecNumber,
+ SpecOperatorName,
+ SpecRole,
+ 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;
+}
+
+#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)
+
+/*
+ * 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 'O':
+ specifier = SpecOperatorName;
+ break;
+ case 'n':
+ specifier = SpecNumber;
+ break;
+ case 'R':
+ specifier = SpecRole;
+ 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);
+ }
+}
+
+/*
+ * 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;
+ char *typmodstr;
+ 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);
+ pfree(schema);
+ pfree(typename);
+ pfree(typmodstr);
+}
+
+/*
+ * Expand a JSON value as an operator name. The value may contain element
+ * "schemaname" (optional).
+ */
+static void
+expand_jsonval_operator(StringInfo buf, JsonbValue *jsonval)
+{
+ char *str;
+ JsonbContainer *data = jsonval->val.binary.data;
+
+ str = find_string_in_jsonbcontainer(data, "schemaname", true, NULL);
+ /* Schema might be NULL or empty */
+ if (str != NULL && str[0] != '\0')
+ {
+ appendStringInfo(buf, "%s.", quote_identifier(str));
+ pfree(str);
+ }
+
+ str = find_string_in_jsonbcontainer(data, "objname", false, NULL);
+ if (!str)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("missing operator name"));
+
+ appendStringInfoString(buf, str);
+ pfree(str);
+}
+
+/*
+ * 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;
+
+ 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;
+
+ 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 a JSON value as a role name. If the 'is_public' element is set to
+ * true, PUBLIC is expanded (no quotes); otherwise, expand the given role name,
+ * quoting as an identifier.
+ */
+static void
+expand_jsonval_role(StringInfo buf, JsonbValue *jsonval)
+{
+ json_trivalue is_public;
+
+ is_public = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+ "is_public");
+ if (is_public == tv_true)
+ appendStringInfoString(buf, "PUBLIC");
+ else
+ {
+ char *rolename;
+
+ rolename = find_string_in_jsonbcontainer(jsonval->val.binary.data,
+ "rolename", false, NULL);
+ if (rolename)
+ {
+ appendStringInfoString(buf, quote_identifier(rolename));
+ pfree(rolename);
+ }
+ }
+}
+
+/*
+ * 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 SpecOperatorName:
+ if (jsonval->type != jbvBinary)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("expected JSON struct for %%O element \"%s\", got %d",
+ param, jsonval->type));
+ expand_jsonval_operator(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;
+
+ case SpecRole:
+ if (jsonval->type != jbvBinary)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("expected JSON struct for %%R element \"%s\", got %d",
+ param, jsonval->type));
+ expand_jsonval_role(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
+ * O expand as an operator 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)
+ * R expand as a role name (possibly quoted name, or PUBLIC)
+ *
+ * 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.
+ *------
+ */
+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..cfc36b02d0 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -36,8 +36,10 @@
#include "lib/ilist.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
+#include "parser/parser.h"
#include "pgstat.h"
#include "tcop/deparse_utility.h"
+#include "tcop/ddl_deparse.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -48,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,
@@ -130,7 +94,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
strcmp(stmt->eventname, "ddl_command_end") != 0 &&
strcmp(stmt->eventname, "sql_drop") != 0 &&
- strcmp(stmt->eventname, "table_rewrite") != 0)
+ strcmp(stmt->eventname, "table_rewrite") != 0 &&
+ strcmp(stmt->eventname, "table_init_write") != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized event name \"%s\"",
@@ -156,7 +121,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
/* Validate tag list, if any. */
if ((strcmp(stmt->eventname, "ddl_command_start") == 0 ||
strcmp(stmt->eventname, "ddl_command_end") == 0 ||
- strcmp(stmt->eventname, "sql_drop") == 0)
+ strcmp(stmt->eventname, "sql_drop") == 0 ||
+ strcmp(stmt->eventname, "table_init_write") == 0)
&& tags != NULL)
validate_ddl_tags("tag", tags);
else if (strcmp(stmt->eventname, "table_rewrite") == 0
@@ -560,6 +526,7 @@ EventTriggerCommonSetup(Node *parsetree,
List *cachelist;
ListCell *lc;
List *runlist = NIL;
+ int pub_deparse_func_cnt = 0;
/*
* We want the list of command tags for which this procedure is actually
@@ -582,7 +549,8 @@ EventTriggerCommonSetup(Node *parsetree,
dbgtag = CreateCommandTag(parsetree);
if (event == EVT_DDLCommandStart ||
event == EVT_DDLCommandEnd ||
- event == EVT_SQLDrop)
+ event == EVT_SQLDrop ||
+ event == EVT_TableInitWrite)
{
if (!command_tag_event_trigger_ok(dbgtag))
elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag));
@@ -609,6 +577,12 @@ EventTriggerCommonSetup(Node *parsetree,
* once we do anything at all that touches the catalogs, an invalidation
* might leave cachelist pointing at garbage, so we must do this before we
* can do much else.
+ *
+ * Special handling for event triggers created as part of publications.
+ * If there are multiple publications which publish ddls, only one set of the
+ * event trigger functions need to be invoked. The ddl deparse event triggers
+ * write to WAL, so no need to duplicate it as all walsenders will read the same
+ * WAL.
*/
foreach(lc, cachelist)
{
@@ -616,8 +590,25 @@ EventTriggerCommonSetup(Node *parsetree,
if (filter_event_trigger(tag, item))
{
- /* We must plan to fire this trigger. */
- runlist = lappend_oid(runlist, item->fnoid);
+ static const char *trigger_func_prefix = "publication_deparse_%s";
+ char trigger_func_name[NAMEDATALEN];
+ Oid pub_funcoid;
+ List *pubfuncname;
+
+ /* Get function oid of the publication's ddl deparse event trigger */
+ snprintf(trigger_func_name, sizeof(trigger_func_name), trigger_func_prefix,
+ eventstr);
+ pubfuncname = SystemFuncName(trigger_func_name);
+ pub_funcoid = LookupFuncName(pubfuncname, 0, NULL, true);
+
+ if (item->fnoid != pub_funcoid)
+ runlist = lappend_oid(runlist, item->fnoid);
+ else
+ {
+ /* Only the first ddl deparse event trigger needs to be invoked */
+ if (pub_deparse_func_cnt++ == 0)
+ runlist = lappend_oid(runlist, item->fnoid);
+ }
}
}
@@ -865,6 +856,140 @@ EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason)
CommandCounterIncrement();
}
+/*
+ * EventTriggerTableInitWriteStart
+ * Prepare to receive data on an CREATE TABLE AS/SELET INTO command about
+ * to be executed.
+ */
+void
+EventTriggerTableInitWriteStart(Node *parsetree)
+{
+ MemoryContext oldcxt;
+ CollectedCommand *command;
+ CreateTableAsStmt *stmt = (CreateTableAsStmt *)parsetree;
+
+ /* ignore if event trigger context not set, or collection disabled */
+ if (!currentEventTriggerState ||
+ currentEventTriggerState->commandCollectionInhibited)
+ return;
+
+ oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+ command = palloc(sizeof(CollectedCommand));
+
+ command->type = (stmt->objtype == OBJECT_TABLE) ? SCT_CreateTableAs : SCT_Simple;
+ command->in_extension = creating_extension;
+ command->d.ctas.address = InvalidObjectAddress;
+ command->d.ctas.real_create = NULL;
+ command->parsetree = copyObject(parsetree);
+
+ command->parent = currentEventTriggerState->currentCommand;
+ currentEventTriggerState->currentCommand = command;
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerTableInitWriteEnd
+ * Finish up saving an CREATE TABLE AS/SELECT INTO command.
+ *
+ * FIXME this API isn't considering the possibility that an xact/subxact is
+ * aborted partway through. Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerTableInitWriteEnd(ObjectAddress address)
+{
+ CollectedCommand *parent;
+ CreateTableAsStmt *stmt;
+
+ /* ignore if event trigger context not set, or collection disabled */
+ if (!currentEventTriggerState ||
+ currentEventTriggerState->commandCollectionInhibited)
+ return;
+
+ stmt = (CreateTableAsStmt *)currentEventTriggerState->currentCommand->parsetree;
+
+ if (stmt->objtype == OBJECT_TABLE)
+ {
+ parent = currentEventTriggerState->currentCommand->parent;
+
+ pfree(currentEventTriggerState->currentCommand);
+
+ currentEventTriggerState->currentCommand = parent;
+ }
+ else
+ {
+ MemoryContext oldcxt;
+
+ oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+ currentEventTriggerState->currentCommand->d.simple.address = address;
+ currentEventTriggerState->commandList =
+ lappend(currentEventTriggerState->commandList,
+ currentEventTriggerState->currentCommand);
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+}
+
+/*
+ * Fire table_init_rewrite triggers.
+ */
+void
+EventTriggerTableInitWrite(Node *real_create, ObjectAddress address)
+{
+ List *runlist;
+ EventTriggerData trigdata;
+ CollectedCommand *command;
+
+ /*
+ * See EventTriggerDDLCommandStart for a discussion about why event
+ * triggers are disabled in single user mode.
+ */
+ if (!IsUnderPostmaster)
+ return;
+
+ /*
+ * Also do nothing if our state isn't set up, which it won't be if there
+ * weren't any relevant event triggers at the start of the current DDL
+ * command. This test might therefore seem optional, but it's
+ * *necessary*, because EventTriggerCommonSetup might find triggers that
+ * didn't exist at the time the command started.
+ */
+ if (!currentEventTriggerState)
+ return;
+
+ /* Do nothing if no command was collected. */
+ if (!currentEventTriggerState->currentCommand)
+ return;
+
+ command = currentEventTriggerState->currentCommand;
+
+ runlist = EventTriggerCommonSetup(command->parsetree,
+ EVT_TableInitWrite,
+ "table_init_write",
+ &trigdata);
+ if (runlist == NIL)
+ return;
+
+ /* Set the real CreateTable statment and object address */
+ command->d.ctas.real_create = real_create;
+ command->d.ctas.address = address;
+
+ /* Run the triggers. */
+ EventTriggerInvoke(runlist, &trigdata);
+
+ /* Cleanup. */
+ list_free(runlist);
+
+ /*
+ * Make sure anything the event triggers did will be visible to the main
+ * command.
+ */
+ CommandCounterIncrement();
+}
+
/*
* Invoke each event trigger in a list of event triggers.
*/
@@ -1146,7 +1271,8 @@ trackDroppedObjectsNeeded(void)
*/
return (EventCacheLookup(EVT_SQLDrop) != NIL) ||
(EventCacheLookup(EVT_TableRewrite) != NIL) ||
- (EventCacheLookup(EVT_DDLCommandEnd) != NIL);
+ (EventCacheLookup(EVT_DDLCommandEnd) != NIL) ||
+ (EventCacheLookup(EVT_TableInitWrite) != NIL);
}
/*
@@ -1537,6 +1663,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);
@@ -1570,7 +1697,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;
@@ -1590,12 +1717,140 @@ 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)
+ {
+ char *defexpr;
+
+ defexpr = nodeToString(def->cooked_default);
+ newsub->usingexpr = TextDatumGetCString(DirectFunctionCall2(pg_get_expr,
+ CStringGetTextDatum(defexpr),
+ RelationGetRelid(rel)));
+ }
+ 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.
@@ -1863,6 +2118,7 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
case SCT_AlterOpFamily:
case SCT_CreateOpClass:
case SCT_AlterTSConfig:
+ case SCT_CreateTableAs:
{
char *identity;
char *type;
@@ -1880,6 +2136,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
addr = cmd->d.createopc.address;
else if (cmd->type == SCT_AlterTSConfig)
addr = cmd->d.atscfg.address;
+ else if (cmd->type == SCT_CreateTableAs)
+ addr = cmd->d.ctas.address;
/*
* If an object was dropped in the same command we may end
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 42cced9ebe..2844d36521 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',
+ 'ddl_deparse.c',
+ 'ddl_json.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 d9bbeafd82..919412122b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4743,6 +4743,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);
@@ -5014,6 +5017,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
{
ObjectAddress address = InvalidObjectAddress;
Relation rel = tab->rel;
+ bool commandCollected = false;
switch (cmd->subtype)
{
@@ -5137,6 +5141,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 =
@@ -5309,8 +5315,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/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b0f6fe4fa6..21a9cae77c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1395,6 +1395,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/tcop/utility.c b/src/backend/tcop/utility.c
index 30b51bf4d3..4501cc337c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1675,8 +1675,11 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateTableAsStmt:
+ EventTriggerTableInitWriteStart(parsetree);
address = ExecCreateTableAs(pstate, (CreateTableAsStmt *) parsetree,
params, queryEnv, qc);
+ EventTriggerTableInitWriteEnd(address);
+ commandCollected = true;
break;
case T_RefreshMatViewStmt:
@@ -2206,6 +2209,112 @@ UtilityContainsQuery(Node *parsetree)
}
}
+/*
+ * Return the given object type as a string.
+ *
+ * If isgrant is true, then this function is called while deparsing GRANT
+ * statement and some object names are replaced.
+ */
+const char *
+stringify_objtype(ObjectType objtype, bool isgrant)
+{
+ switch (objtype)
+ {
+ case OBJECT_AGGREGATE:
+ return "AGGREGATE";
+ case OBJECT_CAST:
+ return "CAST";
+ case OBJECT_COLLATION:
+ return "COLLATION";
+ case OBJECT_COLUMN:
+ return isgrant ? "TABLE" : "COLUMN";
+ case OBJECT_CONVERSION:
+ return "CONVERSION";
+ case OBJECT_DATABASE:
+ return "DATABASE";
+ case OBJECT_DOMAIN:
+ return "DOMAIN";
+ case OBJECT_EVENT_TRIGGER:
+ return "EVENT TRIGGER";
+ case OBJECT_EXTENSION:
+ return "EXTENSION";
+ case OBJECT_FDW:
+ return "FOREIGN DATA WRAPPER";
+ case OBJECT_FOREIGN_SERVER:
+ return isgrant ? "FOREIGN SERVER" : "SERVER";
+ case OBJECT_FOREIGN_TABLE:
+ return "FOREIGN TABLE";
+ case OBJECT_FUNCTION:
+ return "FUNCTION";
+ case OBJECT_INDEX:
+ return "INDEX";
+ case OBJECT_LANGUAGE:
+ return "LANGUAGE";
+ case OBJECT_LARGEOBJECT:
+ return "LARGE OBJECT";
+ case OBJECT_MATVIEW:
+ return "MATERIALIZED VIEW";
+ case OBJECT_OPCLASS:
+ return "OPERATOR CLASS";
+ case OBJECT_OPERATOR:
+ return "OPERATOR";
+ case OBJECT_OPFAMILY:
+ return "OPERATOR FAMILY";
+ case OBJECT_POLICY:
+ return "POLICY";
+ case OBJECT_PROCEDURE:
+ return "PROCEDURE";
+ case OBJECT_ROLE:
+ return "ROLE";
+ case OBJECT_ROUTINE:
+ return "ROUTINE";
+ case OBJECT_RULE:
+ return "RULE";
+ case OBJECT_SCHEMA:
+ return "SCHEMA";
+ case OBJECT_SEQUENCE:
+ return "SEQUENCE";
+ case OBJECT_STATISTIC_EXT:
+ return "STATISTICS";
+ case OBJECT_TABLE:
+ return "TABLE";
+ case OBJECT_TABLESPACE:
+ return "TABLESPACE";
+ case OBJECT_TRIGGER:
+ return "TRIGGER";
+ case OBJECT_TSCONFIGURATION:
+ return "TEXT SEARCH CONFIGURATION";
+ case OBJECT_TSDICTIONARY:
+ return "TEXT SEARCH DICTIONARY";
+ case OBJECT_TSPARSER:
+ return "TEXT SEARCH PARSER";
+ case OBJECT_TSTEMPLATE:
+ return "TEXT SEARCH TEMPLATE";
+ case OBJECT_TYPE:
+ return "TYPE";
+ case OBJECT_USER_MAPPING:
+ return "USER MAPPING";
+ case OBJECT_VIEW:
+ return "VIEW";
+ case OBJECT_ACCESS_METHOD:
+ case OBJECT_AMOP:
+ case OBJECT_AMPROC:
+ case OBJECT_ATTRIBUTE:
+ case OBJECT_DEFAULT:
+ case OBJECT_DEFACL:
+ case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PARAMETER_ACL:
+ case OBJECT_PUBLICATION:
+ case OBJECT_PUBLICATION_NAMESPACE:
+ case OBJECT_PUBLICATION_REL:
+ case OBJECT_SUBSCRIPTION:
+ case OBJECT_TABCONSTRAINT:
+ case OBJECT_TRANSFORM:
+ elog(ERROR, "unsupported object type %d", objtype);
+ }
+
+ return "???"; /* keep compiler quiet */
+}
/*
* AlterObjectTypeCommandTag
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 12402a0637..7b476adb23 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)
@@ -363,7 +361,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 461735e84f..c8f5471b18 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -499,22 +499,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 ")
@@ -1883,6 +1876,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.
*/
@@ -2130,6 +2131,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
*/
@@ -11616,7 +11627,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)
{
@@ -12010,7 +12021,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)
@@ -12396,7 +12407,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;
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
index 1f5e7eb4c6..f2a9f5dcc2 100644
--- a/src/backend/utils/cache/evtcache.c
+++ b/src/backend/utils/cache/evtcache.c
@@ -167,6 +167,8 @@ BuildEventTriggerCache(void)
event = EVT_SQLDrop;
else if (strcmp(evtevent, "table_rewrite") == 0)
event = EVT_TableRewrite;
+ else if (strcmp(evtevent, "table_init_write") == 0)
+ event = EVT_TableInitWrite;
else
continue;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f9f2642201..742df47381 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12053,4 +12053,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/event_trigger.h b/src/include/commands/event_trigger.h
index 5ed6ece555..cba4e72455 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
@@ -55,6 +94,10 @@ extern void EventTriggerDDLCommandEnd(Node *parsetree);
extern void EventTriggerSQLDrop(Node *parsetree);
extern void EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason);
+extern void EventTriggerTableInitWriteStart(Node *parsetree);
+extern void EventTriggerTableInitWrite(Node *parsetree, ObjectAddress address);
+extern void EventTriggerTableInitWriteEnd(ObjectAddress address);
+
extern bool EventTriggerBeginCompleteQuery(void);
extern void EventTriggerEndCompleteQuery(void);
extern bool trackDroppedObjectsNeeded(void);
@@ -71,7 +114,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/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/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cc7b32b279..153ba297b4 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/tcop/ddl_deparse.h b/src/include/tcop/ddl_deparse.h
new file mode 100644
index 0000000000..1a2702c5ac
--- /dev/null
+++ b/src/include/tcop/ddl_deparse.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddl_deparse.h
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/ddl_deparse.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DDL_DEPARSE_H
+#define DDL_DEPARSE_H
+
+#include "tcop/deparse_utility.h"
+
+extern char *deparse_utility_command(CollectedCommand *cmd, bool verbose_mode);
+extern char *deparse_ddl_json_to_string(char *jsonb);
+extern char *deparse_drop_command(const char *objidentity, const char *objecttype,
+ DropBehavior behavior);
+
+#endif /* DDL_DEPARSE_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index b585810b9a..a4a12377b8 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -29,7 +29,8 @@ typedef enum CollectedCommandType
SCT_AlterOpFamily,
SCT_AlterDefaultPrivileges,
SCT_CreateOpClass,
- SCT_AlterTSConfig
+ SCT_AlterTSConfig,
+ SCT_CreateTableAs
} CollectedCommandType;
/*
@@ -39,6 +40,7 @@ typedef struct CollectedATSubcmd
{
ObjectAddress address; /* affected column, constraint, index, ... */
Node *parsetree;
+ char *usingexpr;
} CollectedATSubcmd;
typedef struct CollectedCommand
@@ -62,6 +64,7 @@ typedef struct CollectedCommand
{
Oid objectId;
Oid classId;
+ bool rewrite;
List *subcmds;
} alterTable;
@@ -100,6 +103,13 @@ typedef struct CollectedCommand
{
ObjectType objtype;
} defprivs;
+
+ /* CREATE TABLE AS */
+ struct
+ {
+ ObjectAddress address;
+ Node *real_create;
+ } ctas;
} d;
struct CollectedCommand *parent; /* when nested */
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 59e64aea07..a68ce3d336 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, bool isgrant);
+
static inline const char *
CreateCommandName(Node *parsetree)
{
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2f8b46d6da..c90c32bff1 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -127,6 +127,7 @@ extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
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/evtcache.h b/src/include/utils/evtcache.h
index d340026518..91d4bdd6b3 100644
--- a/src/include/utils/evtcache.h
+++ b/src/include/utils/evtcache.h
@@ -22,7 +22,8 @@ typedef enum
EVT_DDLCommandStart,
EVT_DDLCommandEnd,
EVT_SQLDrop,
- EVT_TableRewrite
+ EVT_TableRewrite,
+ EVT_TableInitWrite
} EventTriggerEvent;
typedef struct
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 1a42d9f39b..9adc589173 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -26,9 +26,11 @@ extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
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);
@@ -40,7 +42,14 @@ 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);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3219ea5f05..852ee60934 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1626,6 +1626,9 @@ OSInfo
OSSLCipher
OSSLDigest
OVERLAPPED
+ObjElem
+ObjTree
+ObjType
ObjectAccessDrop
ObjectAccessNamespaceSearch
ObjectAccessPostAlter
@@ -3202,6 +3205,7 @@ compare_context
config_var_value
contain_aggs_of_level_context
convert_testexpr_context
+convSpecifier
copy_data_dest_cb
copy_data_source_cb
core_YYSTYPE
--
2.30.0.windows.2
[application/octet-stream] 0005-Apply-the-DDL-change-as-that-same-user-th-2023_04_07-2.patch (23.1K, 3-0005-Apply-the-DDL-change-as-that-same-user-th-2023_04_07-2.patch)
download | inline diff:
From 396a8071fc96ca7d81d2da86a46318c0a827b8d0 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <houzj.fnst@cn.fujitsu.com>
Date: Fri, 7 Apr 2023 09:42:40 +0800
Subject: [PATCH 5/6] Apply the DDL change as that same user that executed the
1. Change event trigger functions to collect the current role in
CollectedCommand.
2. Change Deparser function deparse_utility_command to encode owner role in the
top-level element such as {myowner:role_name, fmt:..., identity:...} of the
deparsed jsonb output for commands that create database objects. Also change
function deparse_ddl_json_to_string to retrieve the myowner element from a
jsonb string.
3. Introduce a new subscription option match_ddl_owner: when turned on, the
apply worker will apply DDL messages in the role retrieved from the "myowner"
field of the deparsed jsonb string. The default value of match_ddl_owner is on.
---
src/backend/catalog/pg_subscription.c | 1 +
src/backend/commands/ddl_deparse.c | 62 ++++++++++++++++----
src/backend/commands/ddl_json.c | 25 +++++++-
src/backend/commands/event_trigger.c | 5 ++
src/backend/commands/subscriptioncmds.c | 28 ++++++++-
src/backend/replication/logical/ddltrigger.c | 6 +-
src/backend/replication/logical/worker.c | 22 ++++++-
src/bin/pg_dump/pg_dump.c | 13 +++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 8 ++-
src/include/catalog/pg_subscription.h | 5 ++
src/include/tcop/ddl_deparse.h | 4 +-
src/include/tcop/deparse_utility.h | 1 +
13 files changed, 154 insertions(+), 27 deletions(-)
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d07f88ce28..2d82fbfad2 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
sub->disableonerr = subform->subdisableonerr;
sub->passwordrequired = subform->subpasswordrequired;
sub->runasowner = subform->subrunasowner;
+ sub->matchddlowner = subform->submatchddlowner;
/* Get conninfo */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/commands/ddl_deparse.c b/src/backend/commands/ddl_deparse.c
index 83f2eeeb0e..5d85070a85 100644
--- a/src/backend/commands/ddl_deparse.c
+++ b/src/backend/commands/ddl_deparse.c
@@ -172,7 +172,7 @@ static ObjElem *new_object(ObjType type, char *name);
static ObjTree *new_objtree_for_qualname_id(Oid classId, Oid objectId);
static ObjElem *new_object_object(ObjTree *value);
static ObjTree *new_objtree_VA(char *fmt, int numobjs,...);
-static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state);
+static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state, char *owner);
static char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
List *dpcontext, List **exprs);
@@ -815,14 +815,39 @@ objtree_fmt_to_jsonb_element(JsonbParseState *state, ObjTree *tree)
}
/*
- * Create a JSONB representation from an ObjTree.
+ * Process the role string into the output parse state.
+ */
+static void
+role_to_jsonb_element(JsonbParseState *state, char *owner)
+{
+ JsonbValue key;
+ JsonbValue val;
+
+ if (owner == NULL)
+ return;
+
+ /* Push the key first */
+ key.type = jbvString;
+ key.val.string.val = "myowner";
+ key.val.string.len = strlen(key.val.string.val);
+ pushJsonbValue(&state, WJB_KEY, &key);
+
+ /* Then process the role string */
+ val.type = jbvString;
+ val.val.string.len = strlen(owner);
+ val.val.string.val = owner;
+ pushJsonbValue(&state, WJB_VALUE, &val);
+}
+
+/*
+ * Create a JSONB representation from an ObjTree and its owner (if given).
*/
static Jsonb *
-objtree_to_jsonb(ObjTree *tree)
+objtree_to_jsonb(ObjTree *tree, char *owner)
{
JsonbValue *value;
- value = objtree_to_jsonb_rec(tree, NULL);
+ value = objtree_to_jsonb_rec(tree, NULL, owner);
return JsonbValueToJsonb(value);
}
@@ -874,7 +899,7 @@ objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
case ObjTypeObject:
/* Recursively add the object into the existing parse state */
- objtree_to_jsonb_rec(object->value.object, state);
+ objtree_to_jsonb_rec(object->value.object, state, NULL);
break;
case ObjTypeArray:
@@ -902,12 +927,13 @@ objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
* Recursive helper for objtree_to_jsonb.
*/
static JsonbValue *
-objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state)
+objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state, char *owner)
{
slist_iter iter;
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ role_to_jsonb_element(state, owner);
objtree_fmt_to_jsonb_element(state, tree);
slist_foreach(iter, &tree->params)
@@ -3400,7 +3426,7 @@ deparse_drop_command(const char *objidentity, const char *objecttype,
"present", ObjTypeBool, behavior == DROP_CASCADE);
append_object_object(stmt, "%{cascade}s", tmp_obj);
- jsonb = objtree_to_jsonb(stmt);
+ jsonb = objtree_to_jsonb(stmt, NULL /* Owner/role can be skipped for drop command */);
command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
return command;
@@ -3624,7 +3650,7 @@ deparse_AlterDependStmt(Oid objectId, Node *parsetree)
* This function should cover all cases handled in ProcessUtilitySlow.
*/
static ObjTree *
-deparse_simple_command(CollectedCommand *cmd)
+deparse_simple_command(CollectedCommand *cmd, bool *include_owner)
{
Oid objectId;
Node *parsetree;
@@ -3641,14 +3667,17 @@ deparse_simple_command(CollectedCommand *cmd)
switch (nodeTag(parsetree))
{
case T_AlterObjectDependsStmt:
+ *include_owner = false;
return deparse_AlterDependStmt(objectId, parsetree);
case T_AlterObjectSchemaStmt:
+ *include_owner = false;
return deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
parsetree,
cmd->d.simple.secondaryObject);
case T_AlterOwnerStmt:
+ *include_owner = false;
return deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree);
case T_CreateStmt:
@@ -3658,6 +3687,7 @@ deparse_simple_command(CollectedCommand *cmd)
return deparse_IndexStmt(objectId, parsetree);
case T_RenameStmt:
+ *include_owner = false;
return deparse_RenameStmt(cmd->d.simple.address, parsetree);
default:
@@ -3670,9 +3700,15 @@ deparse_simple_command(CollectedCommand *cmd)
/*
* Workhorse to deparse a CollectedCommand.
+ *
+ * include_owner indicates if the owner/role of the command should be
+ * included in the deparsed Json output. It is set to false for any commands
+ * that don't CREATE database objects (ALTER commands for example), this is
+ * to avoid encoding and sending the owner to downstream for replay as it is
+ * unnecessary for such commands.
*/
char *
-deparse_utility_command(CollectedCommand *cmd, bool verbose_mode)
+deparse_utility_command(CollectedCommand *cmd, bool include_owner, bool verbose_mode)
{
OverrideSearchPath *overridePath;
MemoryContext oldcxt;
@@ -3713,10 +3749,11 @@ deparse_utility_command(CollectedCommand *cmd, bool verbose_mode)
switch (cmd->type)
{
case SCT_Simple:
- tree = deparse_simple_command(cmd);
+ tree = deparse_simple_command(cmd, &include_owner);
break;
case SCT_AlterTable:
tree = deparse_AlterRelation(cmd);
+ include_owner = false;
break;
case SCT_CreateTableAs:
tree = deparse_CreateTableAsStmt(cmd);
@@ -3731,7 +3768,8 @@ deparse_utility_command(CollectedCommand *cmd, bool verbose_mode)
{
Jsonb *jsonb;
- jsonb = objtree_to_jsonb(tree);
+ jsonb = include_owner ? objtree_to_jsonb(tree, cmd->role) :
+ objtree_to_jsonb(tree, NULL);
command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
}
@@ -3757,7 +3795,7 @@ ddl_deparse_to_json(PG_FUNCTION_ARGS)
CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
char *command;
- command = deparse_utility_command(cmd, true);
+ command = deparse_utility_command(cmd, false, true);
if (command)
PG_RETURN_TEXT_P(cstring_to_text(command));
diff --git a/src/backend/commands/ddl_json.c b/src/backend/commands/ddl_json.c
index 3a57d2697c..76cefb9487 100644
--- a/src/backend/commands/ddl_json.c
+++ b/src/backend/commands/ddl_json.c
@@ -718,7 +718,7 @@ expand_jsonb_array(StringInfo buf, char *param,
* Workhorse for ddl_deparse_expand_command.
*/
char *
-deparse_ddl_json_to_string(char *json_str)
+deparse_ddl_json_to_string(char *json_str, char** owner)
{
Datum d;
Jsonb *jsonb;
@@ -729,6 +729,27 @@ deparse_ddl_json_to_string(char *json_str)
d = DirectFunctionCall1(jsonb_in, PointerGetDatum(json_str));
jsonb = (Jsonb *) DatumGetPointer(d);
+ if (owner != NULL)
+ {
+ const char *key = "myowner";
+ JsonbValue *value;
+
+ value = getKeyJsonValueFromContainer(&jsonb->root, key, strlen(key), NULL);
+ if (value)
+ {
+ char *str;
+
+ /* value->val.string.val may not be NULL terminated */
+ str = palloc(value->val.string.len + 1);
+ memcpy(str, value->val.string.val, value->val.string.len);
+ str[value->val.string.len] = '\0';
+ *owner = str;
+ }
+ else
+ /* myowner is not given in this jsonb, e.g. for Drop Commands */
+ *owner = NULL;
+ }
+
expand_fmt_recursive(buf, &jsonb->root);
return buf->data;
@@ -765,7 +786,7 @@ ddl_deparse_expand_command(PG_FUNCTION_ARGS)
json_str = text_to_cstring(json);
- PG_RETURN_TEXT_P(cstring_to_text(deparse_ddl_json_to_string(json_str)));
+ PG_RETURN_TEXT_P(cstring_to_text(deparse_ddl_json_to_string(json_str, NULL)));
}
/*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71d2c43afc..38c11f000c 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -881,6 +881,7 @@ EventTriggerTableInitWriteStart(Node *parsetree)
command->type = (stmt->objtype == OBJECT_TABLE) ? SCT_CreateTableAs : SCT_Simple;
command->in_extension = creating_extension;
+ command->role = GetUserNameFromId(GetUserId(), false);
command->d.ctas.address = InvalidObjectAddress;
command->d.ctas.real_create = NULL;
command->parsetree = copyObject(parsetree);
@@ -1626,6 +1627,7 @@ EventTriggerCollectSimpleCommand(ObjectAddress address,
command->type = SCT_Simple;
command->in_extension = creating_extension;
+ command->role = GetUserNameFromId(GetUserId(), false);
command->d.simple.address = address;
command->d.simple.secondaryObject = secondaryObject;
@@ -1662,6 +1664,7 @@ EventTriggerAlterTableStart(Node *parsetree)
command->type = SCT_AlterTable;
command->in_extension = creating_extension;
+ command->role = GetUserNameFromId(GetUserId(), false);
command->d.alterTable.classId = RelationRelationId;
command->d.alterTable.objectId = InvalidOid;
@@ -1929,6 +1932,7 @@ EventTriggerCollectGrant(InternalGrant *istmt)
command = palloc(sizeof(CollectedCommand));
command->type = SCT_Grant;
command->in_extension = creating_extension;
+ command->role = GetUserNameFromId(GetUserId(), false);
command->d.grant.istmt = icopy;
command->parsetree = NULL;
@@ -1960,6 +1964,7 @@ EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
command = palloc(sizeof(CollectedCommand));
command->type = SCT_AlterOpFamily;
command->in_extension = creating_extension;
+ command->role = GetUserNameFromId(GetUserId(), false);
ObjectAddressSet(command->d.opfam.address,
OperatorFamilyRelationId, opfamoid);
command->d.opfam.operators = operators;
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 3251d89ba8..727a6fc9c0 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
#define SUBOPT_RUN_AS_OWNER 0x00001000
#define SUBOPT_LSN 0x00002000
#define SUBOPT_ORIGIN 0x00004000
+#define SUBOPT_MATCH_DDL_OWNER 0x00008000
/* check if the 'val' has 'bits' set */
#define IsSet(val, bits) (((val) & (bits)) == (bits))
@@ -95,6 +96,7 @@ typedef struct SubOpts
bool disableonerr;
bool passwordrequired;
bool runasowner;
+ bool matchddlowner;
char *origin;
XLogRecPtr lsn;
} SubOpts;
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
opts->runasowner = false;
if (IsSet(supported_opts, SUBOPT_ORIGIN))
opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+ if (IsSet(supported_opts, SUBOPT_MATCH_DDL_OWNER))
+ opts->matchddlowner = true;
/* Parse options */
foreach(lc, stmt_options)
@@ -353,6 +357,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
opts->specified_opts |= SUBOPT_LSN;
opts->lsn = lsn;
}
+ else if (IsSet(supported_opts, SUBOPT_MATCH_DDL_OWNER) &&
+ strcmp(defel->defname, "match_ddl_owner") == 0)
+ {
+ if (IsSet(opts->specified_opts, SUBOPT_MATCH_DDL_OWNER))
+ errorConflictingDefElem(defel, pstate);
+
+ opts->specified_opts |= SUBOPT_MATCH_DDL_OWNER;
+ opts->matchddlowner = defGetBoolean(defel);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
- SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+ SUBOPT_RUN_AS_OWNER | SUBOPT_MATCH_DDL_OWNER |
+ SUBOPT_ORIGIN);
parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
/*
@@ -708,6 +722,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
publicationListToArray(publications);
values[Anum_pg_subscription_suborigin - 1] =
CStringGetTextDatum(opts.origin);
+ values[Anum_pg_subscription_submatchddlowner - 1] = BoolGetDatum(opts.matchddlowner);
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
@@ -1130,7 +1145,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
SUBOPT_STREAMING | SUBOPT_DISABLE_ON_ERR |
SUBOPT_PASSWORD_REQUIRED |
- SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+ SUBOPT_RUN_AS_OWNER | SUBOPT_MATCH_DDL_OWNER |
+ SUBOPT_ORIGIN);
parse_subscription_options(pstate, stmt->options,
supported_opts, &opts);
@@ -1209,6 +1225,14 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
replaces[Anum_pg_subscription_suborigin - 1] = true;
}
+ if (IsSet(opts.specified_opts, SUBOPT_MATCH_DDL_OWNER))
+ {
+ values[Anum_pg_subscription_submatchddlowner - 1]
+ = BoolGetDatum(opts.matchddlowner);
+ replaces[Anum_pg_subscription_submatchddlowner - 1]
+ = true;
+ }
+
update_tuple = true;
break;
}
diff --git a/src/backend/replication/logical/ddltrigger.c b/src/backend/replication/logical/ddltrigger.c
index fb35cc2c7f..11e33b9b65 100644
--- a/src/backend/replication/logical/ddltrigger.c
+++ b/src/backend/replication/logical/ddltrigger.c
@@ -132,7 +132,7 @@ publication_deparse_table_rewrite(PG_FUNCTION_ARGS)
if (relpersist == RELPERSISTENCE_PERMANENT)
{
/* Deparse the DDL command and WAL log it to allow decoding of the same. */
- json_string = deparse_utility_command(cmd, false);
+ json_string = deparse_utility_command(cmd, true, false);
if (json_string != NULL)
LogLogicalDDLMessage("deparse", cmd->d.alterTable.objectId, DCT_TableAlter,
@@ -211,7 +211,7 @@ publication_deparse_ddl_command_end(PG_FUNCTION_ARGS)
* Deparse the DDL command and WAL log it to allow decoding of the
* same.
*/
- json_string = deparse_utility_command(cmd, false);
+ json_string = deparse_utility_command(cmd, true, false);
if (json_string != NULL)
LogLogicalDDLMessage("deparse", relid, type, json_string,
@@ -284,7 +284,7 @@ publication_deparse_table_init_write(PG_FUNCTION_ARGS)
return PointerGetDatum(NULL);
/* Deparse the DDL command and WAL log it to allow decoding of the same. */
- json_string = deparse_utility_command(cmd, false);
+ json_string = deparse_utility_command(cmd, true, false);
if (json_string != NULL)
LogLogicalDDLMessage("deparse", cmd->d.simple.address.objectId, DCT_SimpleCmd,
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index db379e49fc..ad2c67ac2e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -3395,21 +3395,33 @@ apply_handle_ddl(StringInfo s)
const char *prefix = NULL;
char *message = NULL;
char *ddl_command;
+ char *owner;
Size sz;
List *parsetree_list;
ListCell *parsetree_item;
DestReceiver *receiver;
MemoryContext oldcontext;
const char *save_debug_query_string = debug_query_string;
+ int save_nestlevel = 0;
message = logicalrep_read_ddl(s, &lsn, &prefix, &sz);
/* Make sure we are in a transaction command */
begin_replication_step();
- ddl_command = deparse_ddl_json_to_string(message);
+ ddl_command = deparse_ddl_json_to_string(message, &owner);
debug_query_string = ddl_command;
+ if (MySubscription->matchddlowner && owner)
+ {
+ /*
+ * Set the current role to the owner that executed the command on the
+ * publication server.
+ */
+ save_nestlevel = NewGUCNestLevel();
+ SetConfigOption("role", owner, PGC_INTERNAL, PGC_S_OVERRIDE);
+ }
+
/* DestNone for logical replication */
receiver = CreateDestReceiver(DestNone);
parsetree_list = pg_parse_query(ddl_command);
@@ -3507,6 +3519,14 @@ apply_handle_ddl(StringInfo s)
MemoryContextDelete(per_parsetree_context);
}
+ /*
+ * Restore the GUC variables we set above.
+ */
+ if (save_nestlevel > 0)
+ {
+ AtEOXact_GUC(true, save_nestlevel);
+ }
+
debug_query_string = save_debug_query_string;
end_replication_step();
}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c34d33b9a1..fdcf27dd1e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4648,6 +4648,7 @@ getSubscriptions(Archive *fout)
int i_subpublications;
int i_subbinary;
int i_subpasswordrequired;
+ int i_submatchddlowner;
int i,
ntups;
@@ -4702,11 +4703,13 @@ getSubscriptions(Archive *fout)
if (fout->remoteVersion >= 160000)
appendPQExpBufferStr(query,
" s.suborigin,\n"
- " s.subpasswordrequired\n");
+ " s.subpasswordrequired,\n"
+ " s.submatchddlowner\n");
else
appendPQExpBuffer(query,
" '%s' AS suborigin,\n"
- " 't' AS subpasswordrequired\n",
+ " 't' AS subpasswordrequired,\n"
+ " false AS submatchddlowner\n",
LOGICALREP_ORIGIN_ANY);
appendPQExpBufferStr(query,
@@ -4736,6 +4739,7 @@ getSubscriptions(Archive *fout)
i_subdisableonerr = PQfnumber(res, "subdisableonerr");
i_suborigin = PQfnumber(res, "suborigin");
i_subpasswordrequired = PQfnumber(res, "subpasswordrequired");
+ i_submatchddlowner = PQfnumber(res, "submatchddlowner");
subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
@@ -4768,6 +4772,8 @@ getSubscriptions(Archive *fout)
subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
subinfo[i].subpasswordrequired =
pg_strdup(PQgetvalue(res, i, i_subpasswordrequired));
+ subinfo[i].submatchddlowner =
+ pg_strdup(PQgetvalue(res, i, i_submatchddlowner));
/* Decide whether we want to dump it */
selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4846,6 +4852,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
if (pg_strcasecmp(subinfo->suborigin, LOGICALREP_ORIGIN_ANY) != 0)
appendPQExpBuffer(query, ", origin = %s", subinfo->suborigin);
+ if (strcmp(subinfo->submatchddlowner, "f") == 0)
+ appendPQExpBufferStr(query, ", match_ddl_owner = false");
+
if (strcmp(subinfo->subsynccommit, "off") != 0)
appendPQExpBuffer(query, ", synchronous_commit = %s", fmtId(subinfo->subsynccommit));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 9ae6a38e00..efd63477a8 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -666,6 +666,7 @@ typedef struct _SubscriptionInfo
char *subsynccommit;
char *subpublications;
char *subpasswordrequired;
+ char *submatchddlowner;
} SubscriptionInfo;
/*
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 7068dc0388..df79c79e8d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6521,7 +6521,7 @@ describeSubscriptions(const char *pattern, bool verbose)
PGresult *res;
printQueryOpt myopt = pset.popt;
static const bool translate_columns[] = {false, false, false, false,
- false, false, false, false, false, false, false, false, false};
+ false, false, false, false, false, false, false, false, false, false};
if (pset.sversion < 100000)
{
@@ -6579,9 +6579,11 @@ describeSubscriptions(const char *pattern, bool verbose)
if (pset.sversion >= 160000)
appendPQExpBuffer(&buf,
", suborigin AS \"%s\"\n"
- ", subrunasowner AS \"%s\"\n",
+ ", subrunasowner AS \"%s\"\n"
+ ", submatchddlowner AS \"%s\"\n",
gettext_noop("Origin"),
- gettext_noop("Run as Owner?"));
+ gettext_noop("Run as Owner?"),
+ gettext_noop("Match DDL owner"));
appendPQExpBuffer(&buf,
", subsynccommit AS \"%s\"\n"
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 91d729d62d..be451237f6 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -93,6 +93,9 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
bool subrunasowner; /* True if replication should execute as
* the subscription owner */
+ bool submatchddlowner; /* True if replicated objects by DDL replication
+ * should match the original owner on the publisher */
+
#ifdef CATALOG_VARLEN /* variable-length fields start here */
/* Connection string to the publisher */
text subconninfo BKI_FORCE_NOT_NULL;
@@ -144,6 +147,8 @@ typedef struct Subscription
List *publications; /* List of publication names to subscribe to */
char *origin; /* Only publish data originating from the
* specified origin */
+ bool matchddlowner; /* Indicates if replicated objects by DDL replication
+ * should match the original owner on the publisher */
} Subscription;
/* Disallow streaming in-progress transactions. */
diff --git a/src/include/tcop/ddl_deparse.h b/src/include/tcop/ddl_deparse.h
index 1a2702c5ac..2b7754bfac 100644
--- a/src/include/tcop/ddl_deparse.h
+++ b/src/include/tcop/ddl_deparse.h
@@ -14,8 +14,8 @@
#include "tcop/deparse_utility.h"
-extern char *deparse_utility_command(CollectedCommand *cmd, bool verbose_mode);
-extern char *deparse_ddl_json_to_string(char *jsonb);
+extern char *deparse_utility_command(CollectedCommand *cmd, bool include_owner, bool verbose_mode);
+extern char *deparse_ddl_json_to_string(char *jsonb, char** owner);
extern char *deparse_drop_command(const char *objidentity, const char *objecttype,
DropBehavior behavior);
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index a4a12377b8..87a761bb3e 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -48,6 +48,7 @@ typedef struct CollectedCommand
CollectedCommandType type;
bool in_extension;
+ char *role;
Node *parsetree;
union
--
2.30.0.windows.2
[application/octet-stream] 0002-DDL-replication-for-Table-DDL-commands-2023_04_07-2.patch (239.9K, 4-0002-DDL-replication-for-Table-DDL-commands-2023_04_07-2.patch)
download | inline diff:
From d78d2b3b6b0ef2356544b05d5c36fcc0260c3385 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <houzj.fnst@cn.fujitsu.com>
Date: Mon, 3 Apr 2023 20:31:23 +0800
Subject: [PATCH 2/6] DDL replication for Table DDL commands
To support DDL replication, it use event trigger and DDL deparsing
facilities. During CREATE PUBLICATION we register a command end trigger that
deparses the DDL (if the DDL is annotated as ddlreplok for DDL replication in
cmdtaglist.h) as a JSON blob, and WAL logs it. The event trigger is automatically
removed at the time of DROP PUBLICATION. The WALSender decodes the WAL and sends
it downstream similar to other DML commands. The subscriber then converts JSON
back to the DDL command string and executes it. In the subscriber, we also add
the newly added rel to pg_subscription_rel so that the DML changes on the new
table can be replicated without having to manually run
"ALTER SUBSCRIPTION ... REFRESH PUBLICATION".
- For non-rewrite ALTER object command and
- CREATE object command:
we deparse the command at ddl_command_end event trigger and WAL log the
deparsed json string. The WALSender decodes the WAL and sends it to
subscriber if the created/altered table is published. It supports most of
ALTER TABLE command except some commands(DDL related to PARTITIONED TABLE
...) that introduced recently which haven't been supported by the current
ddl_deparser, we will support that later.
- For DROP object:
The 'command start' event handler logs a ddl message with the relids of
the tables that are dropped which the output plugin (pgoutput) stores in
its internal data structure after verifying that it is for a table that is
part of the publication. Later the 'command end' event handler sends the
actual drop message. Pgoutput on receiving the command end, only sends out
the drop command only if it is for one of the relids marked for deleting.
The reason we have to do this is because, once the logical decoder
receives the 'command end' message, the relid of the table is no longer
valid as it has been deleted as part of invalidations received for the
drop table command. It is no longer possible to verify if the table is
part of the publication list or not. To make this possible, I have added
two more elements to the ddl xlog and ddl message, (relid and cmdtype).
We could have also handled all this on the subscriber side as well, but
that would mean sending spurious ddl messages for tables that are not part
of the publication.
- For table_rewrite ALTER TABLE command:
(ALTER COLUMN TYPE, ADD COLUMN DEFAULT, SET LOGGED, SET ACCESS METHOD)
we deparse the command and WAL log the deparsed json string at
table_rewrite event trigger. The WALSender decodes the WAL and sends it to
subscriber if the altered table is published. Then, the WALSender will
convert the upcoming rewrite INSERTs to UPDATEs and send them to
subscriber so that the data between publisher and subscriber can always be
consistent. Note that the tables that publish rewrite ddl must have a
replica identity configured in order to be able to replicate the upcoming
rewrite UPDATEs.
We do this way because of two reason:
(1) The data before the rewrite ddl could already be different among
publisher and subscriber. To make sure the extra data in subscriber which
doesn't exist in publisher also get rewritten, we need to let the
subscriber execute the original rewrite ddl to rewrite all the data at
first.
(2) the data after executing rewrite ddl could be different among
publisher and subscriber(due to different functions/operators used during
rewrite), so we need to replicate the rewrite UPDATEs to keep the data
consistent.
TO IMPROVE:
This approach could be improved by letting the subscriber try to update
the extra data itself instead of doing fully rewrite ddl and use the
upcoming rewrite UPDATEs to rewrite the rest data. To achieve this, we
could modify the deparsed json string to temporarily remove the rewrite
part and add some logic in subscriber to update the extra data.
Besides, we may not need to send rewrite changes for all type of rewrite
ddl, for example, it seems fine to skip sending rewrite changes for ALTER
TABLE SET LOGGED as the data in the table doesn't actually be changed. We
could use the deparser and event trigger to filter these ddls and skip
sending rewrite changes for them.
---
contrib/test_decoding/expected/ddl.out | 26 +
contrib/test_decoding/sql/ddl.sql | 5 +
contrib/test_decoding/test_decoding.c | 48 +
doc/src/sgml/logical-replication.sgml | 1049 +++++++++++++++++
src/backend/access/rmgrdesc/Makefile | 1 +
.../access/rmgrdesc/logicalddlmsgdesc.c | 52 +
src/backend/access/rmgrdesc/meson.build | 1 +
src/backend/access/transam/rmgr.c | 1 +
src/backend/catalog/pg_publication.c | 1 +
src/backend/commands/event_trigger.c | 2 +
src/backend/commands/publicationcmds.c | 323 ++++-
src/backend/replication/logical/Makefile | 2 +
src/backend/replication/logical/ddlmessage.c | 84 ++
src/backend/replication/logical/ddltrigger.c | 283 +++++
src/backend/replication/logical/decode.c | 41 +
src/backend/replication/logical/logical.c | 93 ++
.../replication/logical/logicalfuncs.c | 24 +
src/backend/replication/logical/meson.build | 2 +
src/backend/replication/logical/proto.c | 43 +
.../replication/logical/reorderbuffer.c | 135 +++
src/backend/replication/logical/worker.c | 240 ++++
src/backend/replication/pgoutput/pgoutput.c | 268 ++++-
src/backend/tcop/cmdtag.c | 26 +-
src/backend/tcop/utility.c | 6 +-
src/backend/utils/cache/relcache.c | 1 +
src/bin/pg_dump/pg_dump.c | 78 +-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 2 +-
src/bin/pg_waldump/rmgrdesc.c | 1 +
src/bin/psql/describe.c | 33 +-
src/include/access/rmgrlist.h | 1 +
src/include/catalog/pg_proc.dat | 20 +
src/include/catalog/pg_publication.h | 30 +-
src/include/commands/publicationcmds.h | 14 +
src/include/replication/ddlmessage.h | 60 +
src/include/replication/decode.h | 1 +
src/include/replication/logicalproto.h | 4 +
src/include/replication/output_plugin.h | 27 +
src/include/replication/pgoutput.h | 1 +
src/include/replication/reorderbuffer.h | 39 +
src/include/tcop/cmdtag.h | 4 +-
src/include/tcop/cmdtaglist.h | 386 +++---
src/include/utils/rel.h | 2 +
src/test/regress/expected/psql.out | 6 +-
src/test/regress/expected/publication.out | 420 +++----
src/tools/pgindent/typedefs.list | 5 +
46 files changed, 3388 insertions(+), 504 deletions(-)
create mode 100644 src/backend/access/rmgrdesc/logicalddlmsgdesc.c
create mode 100644 src/backend/replication/logical/ddlmessage.c
create mode 100644 src/backend/replication/logical/ddltrigger.c
create mode 100644 src/include/replication/ddlmessage.h
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 9a28b5ddc5..0f51f2b41a 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -831,6 +831,32 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
------
(0 rows)
+SELECT 'ddl msg1' FROM pg_logical_emit_ddl_message('ddl msg1', 16394, 1, 'msg1');
+ ?column?
+----------
+ ddl msg1
+(1 row)
+
+SELECT 'ddl msg2' FROM pg_logical_emit_ddl_message('ddl msg2', 16394, 1, '{"fmt": "CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", "name": "foo", "authorization": {"fmt": "AUTHORIZATION %{authorization_role}I", "present": false, "authorization_role": null}, "if_not_exists": ""}');
+ ?column?
+----------
+ ddl msg2
+(1 row)
+
+SELECT 'ddl msg3' FROM pg_logical_emit_ddl_message('ddl msg3', 16394, 1, '{"fmt": "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D (%{table_elements:, }s) %{inherits}s %{tablespace}s %{on_commit}s %{partition_by}s %{access_method}s %{with_clause}s", "identity": {"objname": "foo", "schemaname": "element_test"}, "inherits": {"fmt": "INHERITS (%{parents:, }D)", "parents": null, "present": false}, "on_commit": {"fmt": "ON COMMIT %{on_commit_value}s", "present": false, "on_commit_value": null}, "tablespace": {"fmt": "TABLESPACE %{tablespace}I", "present": false, "tablespace": null}, "persistence": "", "with_clause": {"fmt": "WITH", "present": false}, "partition_by": {"fmt": "PARTITION BY %{definition}s", "present": false, "definition": null}, "access_method": {"fmt": "USING %{access_method}I", "present": false, "access_method": null}, "if_not_exists": "", "table_elements": [{"fmt": "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s %{not_null}s %{default}s %{generated_column}s", "name": "id", "type": "column", "coltype": {"typmod": "", "typarray": false, "typename": "int4", "schemaname": "pg_catalog"}, "default": {"fmt": "DEFAULT", "present": false}, "not_null": "", "collation": {"fmt": "COLLATE", "present": false}, "colstorage": "plain", "compression": {"fmt": "COMPRESSION %{compression_method}I", "present": false, "compression_method": null}, "generated_column": {"fmt": "GENERATED ALWAYS AS", "present": false}}]}}');
+ ?column?
+----------
+ ddl msg3
+(1 row)
+
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+ data
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ message: prefix: ddl msg1, relid: 16394, cmdtype: Drop start, sz: 4 content:msg1
+ message: prefix: ddl msg2, relid: 16394, cmdtype: Drop start, sz: 217 content:{"fmt": "CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", "name": "foo", "authorization": {"fmt": "AUTHORIZATION %{authorization_role}I", "present": false, "authorization_role": null}, "if_not_exists": ""}
+ message: prefix: ddl msg3, relid: 16394, cmdtype: Drop start, sz: 1396 content:{"fmt": "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D (%{table_elements:, }s) %{inherits}s %{tablespace}s %{on_commit}s %{partition_by}s %{access_method}s %{with_clause}s", "identity": {"objname": "foo", "schemaname": "element_test"}, "inherits": {"fmt": "INHERITS (%{parents:, }D)", "parents": null, "present": false}, "on_commit": {"fmt": "ON COMMIT %{on_commit_value}s", "present": false, "on_commit_value": null}, "tablespace": {"fmt": "TABLESPACE %{tablespace}I", "present": false, "tablespace": null}, "persistence": "", "with_clause": {"fmt": "WITH", "present": false}, "partition_by": {"fmt": "PARTITION BY %{definition}s", "present": false, "definition": null}, "access_method": {"fmt": "USING %{access_method}I", "present": false, "access_method": null}, "if_not_exists": "", "table_elements": [{"fmt": "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s %{not_null}s %{default}s %{generated_column}s", "name": "id", "type": "column", "coltype": {"typmod": "", "typarray": false, "typename": "int4", "schemaname": "pg_catalog"}, "default": {"fmt": "DEFAULT", "present": false}, "not_null": "", "collation": {"fmt": "COLLATE", "present": false}, "colstorage": "plain", "compression": {"fmt": "COMPRESSION %{compression_method}I", "present": false, "compression_method": null}, "generated_column": {"fmt": "GENERATED ALWAYS AS", "present": false}}]}}
+(3 rows)
+
SELECT pg_drop_replication_slot('regression_slot');
pg_drop_replication_slot
--------------------------
diff --git a/contrib/test_decoding/sql/ddl.sql b/contrib/test_decoding/sql/ddl.sql
index 4f76bed72c..1ea5a4b271 100644
--- a/contrib/test_decoding/sql/ddl.sql
+++ b/contrib/test_decoding/sql/ddl.sql
@@ -431,6 +431,11 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
-- done, free logical replication slot
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+SELECT 'ddl msg1' FROM pg_logical_emit_ddl_message('ddl msg1', 16394, 1, 'msg1');
+SELECT 'ddl msg2' FROM pg_logical_emit_ddl_message('ddl msg2', 16394, 1, '{"fmt": "CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", "name": "foo", "authorization": {"fmt": "AUTHORIZATION %{authorization_role}I", "present": false, "authorization_role": null}, "if_not_exists": ""}');
+SELECT 'ddl msg3' FROM pg_logical_emit_ddl_message('ddl msg3', 16394, 1, '{"fmt": "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D (%{table_elements:, }s) %{inherits}s %{tablespace}s %{on_commit}s %{partition_by}s %{access_method}s %{with_clause}s", "identity": {"objname": "foo", "schemaname": "element_test"}, "inherits": {"fmt": "INHERITS (%{parents:, }D)", "parents": null, "present": false}, "on_commit": {"fmt": "ON COMMIT %{on_commit_value}s", "present": false, "on_commit_value": null}, "tablespace": {"fmt": "TABLESPACE %{tablespace}I", "present": false, "tablespace": null}, "persistence": "", "with_clause": {"fmt": "WITH", "present": false}, "partition_by": {"fmt": "PARTITION BY %{definition}s", "present": false, "definition": null}, "access_method": {"fmt": "USING %{access_method}I", "present": false, "access_method": null}, "if_not_exists": "", "table_elements": [{"fmt": "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s %{not_null}s %{default}s %{generated_column}s", "name": "id", "type": "column", "coltype": {"typmod": "", "typarray": false, "typename": "int4", "schemaname": "pg_catalog"}, "default": {"fmt": "DEFAULT", "present": false}, "not_null": "", "collation": {"fmt": "COLLATE", "present": false}, "colstorage": "plain", "compression": {"fmt": "COMPRESSION %{compression_method}I", "present": false, "compression_method": null}, "generated_column": {"fmt": "GENERATED ALWAYS AS", "present": false}}]}}');
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+
SELECT pg_drop_replication_slot('regression_slot');
/* check that the slot is gone */
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index 628c6a2595..763d5c007f 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -14,9 +14,11 @@
#include "catalog/pg_type.h"
+#include "replication/ddlmessage.h"
#include "replication/logical.h"
#include "replication/origin.h"
+#include "tcop/ddl_deparse.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -72,6 +74,12 @@ static void pg_decode_message(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn, XLogRecPtr lsn,
bool transactional, const char *prefix,
Size sz, const char *message);
+static void pg_decode_ddl_message(LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid,
+ DeparsedCommandType cmdtype,
+ Size sz, const char *message);
static bool pg_decode_filter_prepare(LogicalDecodingContext *ctx,
TransactionId xid,
const char *gid);
@@ -135,6 +143,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
cb->filter_by_origin_cb = pg_decode_filter;
cb->shutdown_cb = pg_decode_shutdown;
cb->message_cb = pg_decode_message;
+ cb->ddl_cb = pg_decode_ddl_message;
cb->filter_prepare_cb = pg_decode_filter_prepare;
cb->begin_prepare_cb = pg_decode_begin_prepare_txn;
cb->prepare_cb = pg_decode_prepare_txn;
@@ -750,6 +759,45 @@ pg_decode_message(LogicalDecodingContext *ctx,
OutputPluginWrite(ctx, true);
}
+static void
+pg_decode_ddl_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn, const char *prefix, Oid relid,
+ DeparsedCommandType cmdtype, Size sz, const char *message)
+{
+ OutputPluginPrepareWrite(ctx, true);
+ appendStringInfo(ctx->out, "message: prefix: %s, relid: %u, ",
+ prefix, relid);
+
+ switch(cmdtype)
+ {
+ case DCT_SimpleCmd:
+ appendStringInfo(ctx->out, "cmdtype: Simple, ");
+ break;
+ case DCT_TableDropStart:
+ appendStringInfo(ctx->out, "cmdtype: Drop start, ");
+ break;
+ case DCT_TableDropEnd:
+ appendStringInfo(ctx->out, "cmdtype: Drop end, ");
+ break;
+ case DCT_TableAlter:
+ appendStringInfo(ctx->out, "cmdtype: Alter table, ");
+ break;
+ case DCT_ObjectCreate:
+ appendStringInfo(ctx->out, "cmdtype: Create, ");
+ break;
+ case DCT_ObjectDrop:
+ appendStringInfo(ctx->out, "cmdtype: Drop, ");
+ break;
+ default:
+ appendStringInfo(ctx->out, "cmdtype: Invalid, ");
+ break;
+ }
+
+ appendStringInfo(ctx->out, "sz: %zu content:", sz);
+ appendBinaryStringInfo(ctx->out, message, sz);
+ OutputPluginWrite(ctx, true);
+}
+
static void
pg_decode_stream_start(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index c65f4aabfd..37214baa3d 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1457,6 +1457,1055 @@ test_sub=# SELECT * FROM t1 ORDER BY id;
</sect1>
+ <sect1 id="logical-replication-ddl">
+ <title>DDL Replication</title>
+ <para>
+ Data Definition Commands (DDLs) can be replicated using logical replication.
+ While enabled this feature automatically replicates supported DDL commands
+ that are successfully executed on a publisher to a subscriber. This is
+ especially useful if you have lots schema changes over time that need replication.
+ </para>
+
+ <para>
+ For example, when enabled a CREATE TABLE command executed on the publisher gets
+ WAL-logged, and forwarded to the subscriber to replay; a subsequent "ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION" is run on the subscriber database so any
+ following DML changes on the new table can be replicated without a hitch.
+ </para>
+
+ <para>
+ DDL replication is disabled by default, it can be enabled at different levels
+ using the ddl PUBLICATION option. This option currently has one level and are
+ only allowed to be set if the PUBLICATION is FOR ALL TABLES or FOR TABLES IN SCHEMA.
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ table: this option enables replication of Table DDL commands,
+ which include:
+ <itemizedlist>
+ <listitem>
+ <para>
+ CREATE/ALTER/DROP TABLE
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ CREATE TABLE AS
+ </para>
+ </listitem>
+
+ </itemizedlist>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect2 id="ddl-replication-option-examples">
+ <title>Examples - Setup DDL Replication on the Publisher</title>
+
+ <para>
+ Enable table ddl replication for an existing Publication:
+<programlisting>
+ALTER PUBLICATION mypub SET (ddl = 'table');
+</programlisting></para>
+
+ </sect2>
+
+ <sect2 id="ddl-replication-supported-commands">
+ <title>Supported DDL commands</title>
+
+ <para>
+ The DDL commands supported for logical replication are listed in the following
+ matrix. Note that global commands can be executed at any database and are currently
+ not supported for replication, global commands include ROLE statements, Database
+ statements, TableSpace statements and some of the GrantStmt/RevokeStmt if the target
+ object is a global object. Temporary and unlogged objects will not be replicated.
+ User should take care of creating these objects as these objects might be required
+ by the objects that are replicated (for example creation of tables that might
+ refer to an user created tablespace will fail in the subscriber if the user
+ created tablespaces are not created in the subscriber).
+ </para>
+
+ <table id="ddl-replication-by-command-tag">
+ <title>DDL Replication Support by Command Tag</title>
+ <tgroup cols="3">
+ <colspec colname="col1" colwidth="2*"/>
+ <colspec colname="col2" colwidth="1*"/>
+ <colspec colname="col3" colwidth="1*"/>
+ <thead>
+ <row>
+ <entry>Command Tag</entry>
+ <entry>For Replication</entry>
+ <entry>Notes</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry align="left"><literal>ALTER AGGREGATE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER CAST</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER COLLATION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER CONVERSION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER DATABASE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER DEFAULT PRIVILEGES</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER DOMAIN</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER EVENT TRIGGER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER EXTENSION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER FOREIGN DATA WRAPPER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER FOREIGN TABLE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER FUNCTION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER INDEX</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER LANGUAGE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER LARGE OBJECT</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER MATERIALIZED VIEW</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER OPERATOR</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER OPERATOR CLASS</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER OPERATOR FAMILY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER POLICY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER PROCEDURE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER PUBLICATION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER ROLE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER ROUTINE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER RULER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER SCHEMA</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER SEQUENCE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER SERVER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER STATISTICS</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER SUBSCRIPTION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER SYSTEM</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER TABLE</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER TABLESPACE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER TEXT SEARCH CONFIGURATION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER TEXT SEARCH DICTIONARY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER TEXT SEARCH PARSER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER TEXT SEARCH TEMPLATE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER TRANSFORM</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER TRIGGER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER TYPE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER USER MAPPING</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ALTER VIEW</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ANALYZE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>BEGIN</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CALL</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CHECKPOINT</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CLOSE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CLOSE CURSOR</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CLOSE CURSOR ALL</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CLUSTER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>COMMENT</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>COMMIT</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>COMMIT PREPARED</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>COPY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>COPY FROM</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE ACCESS METHOD</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE AGGREGATE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE CAST</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE COLLATION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE CONSTRAINT</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE CONVERSION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE DATABASE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE DOMAIN</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE EVENT TRIGGER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE EXTENSION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE FOREIGN DATA WRAPPER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE FOREIGN TABLE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE FUNCTION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE INDEX</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE LANGUAGE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE MATERIALIZED VIEW</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE OPERATOR</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE OPERATOR CLASS</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE OPERATOR FAMILY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE POLICY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE PROCEDURE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE PUBLICATION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE ROLE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE RULE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE SCHEMA</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE SEQUENCE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE SERVER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE STATISTICS</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE SUBSCRIPTION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TABLE</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TABLE AS</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TABLESPACE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TEXT SEARCH CONFIGURATION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TEXT SEARCH DICTIONARY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TEXT SEARCH PARSER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TEXT SEARCH TEMPLATE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TRANSFORM</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TRIGGER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE TYPE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE USER MAPPING</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>CREATE VIEW</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DEALLOCATE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DEALLOCATE ALL</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DECLARE CURSOR</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DELETE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DISCARD</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DISCARD ALL</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DISCARD PLANS</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DISCARD SEQUENCES</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DISCARD TEMP</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DO</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP ACCESS METHOD</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP AGGREGATE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP CAST</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP COLLATION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP CONSTRAINT</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP CONVERSION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP DATABASE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP DOMAIN</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP EVENT TRIGGER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP EXTENSION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP FOREIGN DATA WRAPPER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP FOREIGN TABLE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP FUNCTION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP INDEX</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP LANGUAGE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP MATERIALIZED VIEW</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP OPERATOR</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP OPERATOR CLASS</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP OPERATOR FAMILY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP OWNED</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP POLICY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP PROCEDURE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP PUBLICATION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP ROLE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP ROUTINE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP RULE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP SCHEMA</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP SEQUENCE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP SERVER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP STATISTICS</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP SUBSCRIPTION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP TABLE</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP TABLESPACE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP TEXT SEARCH CONFIGURATION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP TEXT SEARCH DICTIONARY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP TEXT SEARCH PARSER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP TEXT SEARCH TEMPLATE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP TRANSFORM</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP TRIGGER</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP TYPE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP USER MAPPING</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>DROP VIEW</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>EXECUTE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>EXPLAIN</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>FETCH</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>GRANT</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>GRANT ROLE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>IMPORT FOREIGN SCHEMA</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>INSERT</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>LISTEN</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>LOAD</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>LOCK TABLE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>MERGE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>MOVE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>NOTIFY</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>PREPARE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>PREPARE TRANSACTION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>REASSIGN OWNED</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>REFRESH MATERIALIZED VIEW</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>REINDEX</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>RELEASE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>RESET</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>REVOKE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>REVOKE ROLE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ROLLBACK</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>ROLLBACK PREPARED</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>SAVEPOINT</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>SECURITY LABEL</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>SET</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>SET CONSTRAINTS</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>SHOW</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>START TRANSACTION</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>UNLISTEN</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>UPDATE</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ <row>
+ <entry align="left"><literal>VACUUM</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
+ <entry align="left"></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect2>
+
+ <sect2 id="ddl-replication-ddl-deparser">
+ <title>DDL Deparser</title>
+ <para>
+ The DDL deparser utility is invoked during the replication of DDLs. The DDL
+ deparser is capable of converting a DDL command into formatted JSON blob, with
+ the necessary information to reconstruct the DDL commands at the destination. The
+ benefit of using the deparser output compared to the original command string
+ includes:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Every database object in the deparsed output is schema-qualified, so that
+ there are no ambiguities even in the face of search_path changes.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The structured JSON and the formatted output makes it possible for
+ machine editing. This can be useful if the subscriber is on a different
+ PG version and has certain DDL syntax differences which need to be
+ resolved before apply.
+ </para>
+ </listitem>
+
+ </itemizedlist>
+ </para>
+
+ <para>
+ The DDL deparser exposes two SQL functions:
+ <itemizedlist>
+ <listitem>
+ <para>
+ ddl_deparse_to_json: given a CollectedCommand from event trigger,
+ returns a JSON representation of it. The command is expanded fully
+ so that there are no ambiguities even in the face of search_path
+ changes.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ ddl_deparse_expand_command: Expand JSON format DDL generated by
+ ddl_deparse_to_json to a plain DDL command.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </sect2>
+
+ </sect1>
+
<sect1 id="logical-replication-conflicts">
<title>Conflicts</title>
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index f88d72fd86..2ff01e69bf 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -18,6 +18,7 @@ OBJS = \
gistdesc.o \
hashdesc.o \
heapdesc.o \
+ logicalddlmsgdesc.o \
logicalmsgdesc.o \
mxactdesc.o \
nbtdesc.o \
diff --git a/src/backend/access/rmgrdesc/logicalddlmsgdesc.c b/src/backend/access/rmgrdesc/logicalddlmsgdesc.c
new file mode 100644
index 0000000000..05e930c90c
--- /dev/null
+++ b/src/backend/access/rmgrdesc/logicalddlmsgdesc.c
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * logicalddlmsgdesc.c
+ * rmgr descriptor routines for replication/logical/ddlmessage.c
+ *
+ * Portions Copyright (c) 2015-2022, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/logicalddlmsgdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "replication/ddlmessage.h"
+
+void
+logicalddlmsg_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_LOGICAL_DDL_MESSAGE)
+ {
+ xl_logical_ddl_message *xlrec = (xl_logical_ddl_message *) rec;
+ char *prefix = xlrec->message;
+ char *message = xlrec->message + xlrec->prefix_size;
+ char *sep = "";
+
+ Assert(prefix[xlrec->prefix_size] != '\0');
+
+ appendStringInfo(buf, "prefix \"%s\"; payload (%zu bytes): ",
+ prefix, xlrec->message_size);
+ appendStringInfo(buf, "relid %u cmdtype %u", xlrec->relid, xlrec->cmdtype);
+ /* Write message payload as a series of hex bytes */
+ for (int cnt = 0; cnt < xlrec->message_size; cnt++)
+ {
+ appendStringInfo(buf, "%s%02X", sep, (unsigned char) message[cnt]);
+ sep = " ";
+ }
+ }
+}
+
+const char *
+logicalddlmsg_identify(uint8 info)
+{
+ if ((info & ~XLR_INFO_MASK) == XLOG_LOGICAL_DDL_MESSAGE)
+ return "DDL";
+
+ return NULL;
+}
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index 166cee67b6..781e2d7713 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -11,6 +11,7 @@ rmgr_desc_sources = files(
'gistdesc.c',
'hashdesc.c',
'heapdesc.c',
+ 'logicalddlmsgdesc.c',
'logicalmsgdesc.c',
'mxactdesc.c',
'nbtdesc.c',
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 7d67eda5f7..678e81ae01 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
#include "fmgr.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "replication/ddlmessage.h"
#include "replication/decode.h"
#include "replication/message.h"
#include "replication/origin.h"
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 47637f28ab..d209602e28 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1028,6 +1028,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubupdate = pubform->pubupdate;
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
+ pub->pubactions.pubddl_table = pubform->pubddl_table;
pub->pubviaroot = pubform->pubviaroot;
ReleaseSysCache(tup);
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index cfc36b02d0..71d2c43afc 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -38,6 +38,8 @@
#include "parser/parse_func.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "replication/ddlmessage.h"
+#include "replication/message.h"
#include "tcop/deparse_utility.h"
#include "tcop/ddl_deparse.h"
#include "tcop/utility.h"
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index f4ba572697..64423ff35f 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -38,10 +38,12 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_clause.h"
#include "parser/parse_collate.h"
#include "parser/parse_relation.h"
+#include "parser/parser.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
@@ -82,21 +84,25 @@ static void PublicationDropSchemas(Oid pubid, List *schemas, bool missing_ok);
static void
parse_publication_options(ParseState *pstate,
List *options,
+ bool for_all_tables,
bool *publish_given,
PublicationActions *pubactions,
bool *publish_via_partition_root_given,
- bool *publish_via_partition_root)
+ bool *publish_via_partition_root,
+ bool *ddl_type_given)
{
ListCell *lc;
*publish_given = false;
*publish_via_partition_root_given = false;
+ *ddl_type_given = false;
/* defaults */
pubactions->pubinsert = true;
pubactions->pubupdate = true;
pubactions->pubdelete = true;
pubactions->pubtruncate = true;
+ pubactions->pubddl_table = false;
*publish_via_partition_root = false;
/* Parse options */
@@ -123,7 +129,7 @@ parse_publication_options(ParseState *pstate,
pubactions->pubtruncate = false;
*publish_given = true;
- publish = defGetString(defel);
+ publish = pstrdup(defGetString(defel));
if (!SplitIdentifierString(publish, ',', &publish_list))
ereport(ERROR,
@@ -158,6 +164,42 @@ parse_publication_options(ParseState *pstate,
*publish_via_partition_root_given = true;
*publish_via_partition_root = defGetBoolean(defel);
}
+ else if (strcmp(defel->defname, "ddl") == 0)
+ {
+ char *ddl_level;
+ List *ddl_list;
+ ListCell *lc3;
+
+ if (*ddl_type_given)
+ errorConflictingDefElem(defel, pstate);
+
+ /*
+ * If ddl option was given only the explicitly listed ddl types
+ * should be published.
+ */
+ pubactions->pubddl_table = false;
+
+ *ddl_type_given = true;
+ ddl_level = defGetString(defel);
+
+ if (!SplitIdentifierString(ddl_level, ',', &ddl_list))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid list syntax for \"ddl\" option"));
+
+ /* Process the option list. */
+ foreach(lc3, ddl_list)
+ {
+ char *publish_opt = (char *) lfirst(lc3);
+
+ if (strcmp(publish_opt, "table") == 0)
+ pubactions->pubddl_table = true;
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized \"ddl\" value: \"%s\"", publish_opt));
+ }
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -599,14 +641,56 @@ check_simple_rowfilter_expr(Node *node, ParseState *pstate)
return check_simple_rowfilter_expr_walker(node, pstate);
}
+/*
+ * Helper function to tranform a where clause.
+ *
+ * Also check the publication row filter expression and throw an error if
+ * anything not permitted or unexpected is encountered.
+ */
+static Node *
+GetTransformWhereClauses(const char *queryString, Relation relation,
+ Node *whereClause, bool check_expr)
+{
+ Node *transformedWhereClause = NULL;
+ ParseNamespaceItem *nsitem;
+ ParseState *pstate;
+
+ /*
+ * A fresh pstate is required so that we only have "this" table in its
+ * rangetable
+ */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+ nsitem = addRangeTableEntryForRelation(pstate, relation,
+ AccessShareLock, NULL,
+ false, false);
+ addNSItemToQuery(pstate, nsitem, false, true, true);
+
+ transformedWhereClause = transformWhereClause(pstate,
+ copyObject(whereClause),
+ EXPR_KIND_WHERE,
+ "PUBLICATION WHERE");
+
+ /* Fix up collation information */
+ assign_expr_collations(pstate, transformedWhereClause);
+
+ /*
+ * We allow only simple expressions in row filters. See
+ * check_simple_rowfilter_expr_walker.
+ */
+ if (check_expr)
+ check_simple_rowfilter_expr(transformedWhereClause, pstate);
+
+ free_parsestate(pstate);
+
+ return transformedWhereClause;
+}
+
/*
* Transform the publication WHERE expression for all the relations in the list,
* ensuring it is coerced to boolean and necessary collation information is
* added if required, and add a new nsitem/RTE for the associated relation to
* the ParseState's namespace list.
- *
- * Also check the publication row filter expression and throw an error if
- * anything not permitted or unexpected is encountered.
*/
static void
TransformPubWhereClauses(List *tables, const char *queryString,
@@ -616,9 +700,6 @@ TransformPubWhereClauses(List *tables, const char *queryString,
foreach(lc, tables)
{
- ParseNamespaceItem *nsitem;
- Node *whereclause = NULL;
- ParseState *pstate;
PublicationRelInfo *pri = (PublicationRelInfo *) lfirst(lc);
if (pri->whereClause == NULL)
@@ -638,34 +719,8 @@ TransformPubWhereClauses(List *tables, const char *queryString,
errdetail("WHERE clause cannot be used for a partitioned table when %s is false.",
"publish_via_partition_root")));
- /*
- * A fresh pstate is required so that we only have "this" table in its
- * rangetable
- */
- pstate = make_parsestate(NULL);
- pstate->p_sourcetext = queryString;
- nsitem = addRangeTableEntryForRelation(pstate, pri->relation,
- AccessShareLock, NULL,
- false, false);
- addNSItemToQuery(pstate, nsitem, false, true, true);
-
- whereclause = transformWhereClause(pstate,
- copyObject(pri->whereClause),
- EXPR_KIND_WHERE,
- "PUBLICATION WHERE");
-
- /* Fix up collation information */
- assign_expr_collations(pstate, whereclause);
-
- /*
- * We allow only simple expressions in row filters. See
- * check_simple_rowfilter_expr_walker.
- */
- check_simple_rowfilter_expr(whereclause, pstate);
-
- free_parsestate(pstate);
-
- pri->whereClause = whereclause;
+ pri->whereClause = GetTransformWhereClauses(queryString, pri->relation,
+ pri->whereClause, true);
}
}
@@ -728,6 +783,140 @@ CheckPubRelationColumnList(char *pubname, List *tables,
}
}
+/*
+ * Helper function to create a event trigger for DDL replication.
+ */
+static void
+CreateDDLReplicaEventTrigger(char *eventname, List *commands, Oid puboid)
+{
+ List *tags = NIL;
+ ListCell *lc;
+ Oid trigger_id;
+ ObjectAddress referenced;
+ ObjectAddress pubaddress;
+ CreateEventTrigStmt *ddl_trigger;
+ char trigger_name[NAMEDATALEN];
+ char trigger_func_name[NAMEDATALEN];
+ static const char *trigger_func_prefix = "publication_deparse_%s";
+
+ ddl_trigger = makeNode(CreateEventTrigStmt);
+
+ snprintf(trigger_name, sizeof(trigger_name), PUB_EVENT_TRIG_PREFIX,
+ eventname, puboid);
+ snprintf(trigger_func_name, sizeof(trigger_func_name), trigger_func_prefix,
+ eventname);
+
+ ddl_trigger->trigname = pstrdup(trigger_name);
+ ddl_trigger->eventname = eventname;
+ ddl_trigger->funcname = SystemFuncName(trigger_func_name);
+
+ foreach(lc, commands)
+ {
+ CommandTag cmdtag = lfirst_int(lc);
+ String *tag = makeString(pstrdup(GetCommandTagName(cmdtag)));
+
+ tags = lappend(tags, tag);
+ }
+
+ ddl_trigger->whenclause = list_make1(makeDefElem("tag", (Node *) tags, -1));
+
+ trigger_id = CreateEventTrigger(ddl_trigger);
+
+ ObjectAddressSet(pubaddress, PublicationRelationId, puboid);
+
+ /*
+ * Register the event triggers as internally dependent on the publication.
+ */
+ ObjectAddressSet(referenced, EventTriggerRelationId, trigger_id);
+ recordDependencyOn(&referenced, &pubaddress, DEPENDENCY_INTERNAL);
+}
+
+/*
+ * If DDL replication is enabled, create event triggers to capture and log any
+ * relevant events.
+ */
+static void
+CreateDDLReplicaEventTriggers(PublicationActions pubactions, Oid puboid)
+{
+ List *start_commands = NIL;
+ List *rewrite_commands = NIL;
+ List *init_commands = NIL;
+ List *end_commands = NIL;
+
+ if (pubactions.pubddl_table)
+ {
+ start_commands = lappend_int(start_commands, CMDTAG_DROP_TABLE);
+ rewrite_commands = lappend_int(rewrite_commands, CMDTAG_ALTER_TABLE);
+
+ init_commands = lappend_int(init_commands, CMDTAG_CREATE_TABLE_AS);
+ init_commands = lappend_int(init_commands, CMDTAG_SELECT_INTO);
+
+ end_commands = lappend_int(end_commands, CMDTAG_CREATE_TABLE);
+ end_commands = lappend_int(end_commands, CMDTAG_ALTER_TABLE);
+ end_commands = lappend_int(end_commands, CMDTAG_DROP_TABLE);
+ }
+
+ /* Create the ddl_command_end event trigger */
+ if (end_commands != NIL)
+ CreateDDLReplicaEventTrigger(PUB_TRIG_EVENT1, end_commands, puboid);
+
+ /* Create the ddl_command_start event trigger */
+ if (start_commands != NIL)
+ CreateDDLReplicaEventTrigger(PUB_TRIG_EVENT2, start_commands, puboid);
+
+ /* Create the table_rewrite event trigger */
+ if (rewrite_commands != NIL)
+ CreateDDLReplicaEventTrigger(PUB_TRIG_EVENT3, rewrite_commands, puboid);
+
+ /* Create the table_init_write event trigger */
+ if (init_commands != NIL)
+ CreateDDLReplicaEventTrigger(PUB_TRIG_EVENT4, init_commands, puboid);
+}
+
+/*
+ * Helper function to drop a event trigger for DDL replication.
+ */
+static void
+DropDDLReplicaEventTrigger(char *event, Oid puboid)
+{
+ char trigger_name[NAMEDATALEN];
+ Oid evtoid;
+ ObjectAddress obj;
+
+ snprintf(trigger_name, sizeof(trigger_name), PUB_EVENT_TRIG_PREFIX,
+ event, puboid);
+
+ evtoid = get_event_trigger_oid(trigger_name, true);
+ if (!OidIsValid(evtoid))
+ return;
+
+ deleteDependencyRecordsForClass(EventTriggerRelationId, evtoid,
+ PublicationRelationId,
+ DEPENDENCY_INTERNAL);
+
+ /*
+ * Ensure that the dependency removal is visible, so that we can drop the
+ * event trigger.
+ */
+ CommandCounterIncrement();
+
+ ObjectAddressSet(obj, EventTriggerRelationId, evtoid);
+ performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+}
+
+/*
+ * Drop all the event triggers which are used for DDL replication.
+ */
+static void
+DropDDLReplicaEventTriggers(Oid puboid)
+{
+ DropDDLReplicaEventTrigger(PUB_TRIG_EVENT1, puboid);
+ DropDDLReplicaEventTrigger(PUB_TRIG_EVENT2, puboid);
+ DropDDLReplicaEventTrigger(PUB_TRIG_EVENT3, puboid);
+ DropDDLReplicaEventTrigger(PUB_TRIG_EVENT4, puboid);
+}
+
+
/*
* Create new publication.
*/
@@ -741,6 +930,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
Datum values[Natts_pg_publication];
HeapTuple tup;
bool publish_given;
+ bool ddl_type_given;
PublicationActions pubactions;
bool publish_via_partition_root_given;
bool publish_via_partition_root;
@@ -781,9 +971,11 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
parse_publication_options(pstate,
stmt->options,
+ stmt->for_all_tables,
&publish_given, &pubactions,
&publish_via_partition_root_given,
- &publish_via_partition_root);
+ &publish_via_partition_root,
+ &ddl_type_given);
puboid = GetNewOidWithIndex(rel, PublicationObjectIndexId,
Anum_pg_publication_oid);
@@ -798,6 +990,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
BoolGetDatum(pubactions.pubdelete);
values[Anum_pg_publication_pubtruncate - 1] =
BoolGetDatum(pubactions.pubtruncate);
+ values[Anum_pg_publication_pubddl_table - 1] =
+ BoolGetDatum(pubactions.pubddl_table);
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
@@ -835,6 +1029,12 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
+ if (pubactions.pubddl_table)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
+ stmt->pubname));
+
rels = OpenTableList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -858,6 +1058,11 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
}
}
+ /*
+ * Create event triggers to allow logging of DDL statements.
+ */
+ CreateDDLReplicaEventTriggers(pubactions, puboid);
+
table_close(rel, RowExclusiveLock);
InvokeObjectPostCreateHook(PublicationRelationId, puboid, 0);
@@ -882,6 +1087,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
bool replaces[Natts_pg_publication];
Datum values[Natts_pg_publication];
bool publish_given;
+ bool ddl_type_given;
PublicationActions pubactions;
bool publish_via_partition_root_given;
bool publish_via_partition_root;
@@ -890,11 +1096,15 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *root_relids = NIL;
ListCell *lc;
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
+
parse_publication_options(pstate,
stmt->options,
+ pubform->puballtables,
&publish_given, &pubactions,
&publish_via_partition_root_given,
- &publish_via_partition_root);
+ &publish_via_partition_root,
+ &ddl_type_given);
pubform = (Form_pg_publication) GETSTRUCT(tup);
@@ -978,6 +1188,18 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
}
+ if (ddl_type_given && pubactions.pubddl_table)
+ {
+ if (root_relids == NIL)
+ root_relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ROOT);
+
+ if (root_relids)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("DDL replication is only supported in FOR ALL TABLES or FOR TABLES IN SCHEMA publication"));
+ }
+
/* Everything ok, form a new tuple. */
memset(values, 0, sizeof(values));
memset(nulls, false, sizeof(nulls));
@@ -998,6 +1220,19 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
replaces[Anum_pg_publication_pubtruncate - 1] = true;
}
+ if (ddl_type_given)
+ {
+ /* Recreate the event triggers if the ddl option is changed. */
+ if (pubform->pubddl_table != pubactions.pubddl_table)
+ {
+ DropDDLReplicaEventTriggers(pubform->oid);
+ CreateDDLReplicaEventTriggers(pubactions, pubform->oid);
+ }
+
+ values[Anum_pg_publication_pubddl_table - 1] = BoolGetDatum(pubactions.pubddl_table);
+ replaces[Anum_pg_publication_pubddl_table - 1] = true;
+ }
+
if (publish_via_partition_root_given)
{
values[Anum_pg_publication_pubviaroot - 1] = BoolGetDatum(publish_via_partition_root);
@@ -1103,6 +1338,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (stmt->action == AP_AddObjects)
{
+ if (pubform->pubddl_table)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
+ stmt->pubname));
+
TransformPubWhereClauses(rels, queryString, pubform->pubviaroot);
publish_schema |= is_schema_publication(pubid);
@@ -1121,6 +1362,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
List *delrels = NIL;
ListCell *oldlc;
+ if (pubform->pubddl_table)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
+ stmt->pubname));
+
TransformPubWhereClauses(rels, queryString, pubform->pubviaroot);
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..b79ddd8cdc 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -16,6 +16,8 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
OBJS = \
applyparallelworker.o \
+ ddlmessage.o \
+ ddltrigger.o \
decode.o \
launcher.o \
logical.o \
diff --git a/src/backend/replication/logical/ddlmessage.c b/src/backend/replication/logical/ddlmessage.c
new file mode 100644
index 0000000000..e11e56e5a3
--- /dev/null
+++ b/src/backend/replication/logical/ddlmessage.c
@@ -0,0 +1,84 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddlmessage.c
+ * Logical DDL messages.
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/replication/logical/ddlmessage.c
+ *
+ * NOTES
+ *
+ * Logical DDL messages allow XLOG logging of DDL command strings that
+ * get passed to the logical decoding plugin. In normal XLOG processing they
+ * are same as NOOP.
+ *
+ * Unlike generic logical messages, these DDL messages have only transactional
+ * mode. Note by default DDLs in PostgreSQL are transactional.
+ *
+ * These messages are part of current transaction and will be sent to
+ * decoding plugin using in a same way as DML operations.
+ *
+ * Every message carries prefix to avoid conflicts between different decoding
+ * plugins. The plugin authors must take extra care to use unique prefix,
+ * good options seems to be for example to use the name of the extension.
+ *
+ * ---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "access/xloginsert.h"
+#include "catalog/namespace.h"
+#include "miscadmin.h"
+#include "nodes/execnodes.h"
+#include "replication/logical.h"
+#include "replication/ddlmessage.h"
+#include "utils/memutils.h"
+
+/*
+ * Write logical decoding DDL message into XLog.
+ */
+XLogRecPtr
+LogLogicalDDLMessage(const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ const char *message, size_t size)
+{
+ xl_logical_ddl_message xlrec;
+
+ /* Ensure we have a valid transaction id. */
+ Assert(IsTransactionState());
+ GetCurrentTransactionId();
+
+ xlrec.dbId = MyDatabaseId;
+ /* Trailing zero is critical; see logicalddlmsg_desc */
+ xlrec.prefix_size = strlen(prefix) + 1;
+ xlrec.message_size = size;
+ xlrec.relid = relid;
+ xlrec.cmdtype = cmdtype;
+
+ XLogBeginInsert();
+ XLogRegisterData((char *) &xlrec, SizeOfLogicalDDLMessage);
+ XLogRegisterData(unconstify(char *, prefix), xlrec.prefix_size);
+ XLogRegisterData(unconstify(char *, message), size);
+
+ /* Allow origin filtering */
+ XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+ return XLogInsert(RM_LOGICALDDLMSG_ID, XLOG_LOGICAL_DDL_MESSAGE);
+}
+
+/*
+ * Redo is basically just noop for logical decoding ddl messages.
+ */
+void
+logicalddlmsg_redo(XLogReaderState *record)
+{
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info != XLOG_LOGICAL_DDL_MESSAGE)
+ elog(PANIC, "logicalddlmsg_redo: unknown op code %u", info);
+
+ /* This is only interesting for logical decoding, see decode.c. */
+}
diff --git a/src/backend/replication/logical/ddltrigger.c b/src/backend/replication/logical/ddltrigger.c
new file mode 100644
index 0000000000..1282340c2b
--- /dev/null
+++ b/src/backend/replication/logical/ddltrigger.c
@@ -0,0 +1,283 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddltrigger.c
+ * Logical DDL messages.
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/replication/logical/ddltrigger.c
+ *
+ * NOTES
+ *
+ * Deparse the ddl command and log it.
+ *
+ * ---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "catalog/pg_class.h"
+#include "commands/event_trigger.h"
+#include "funcapi.h"
+#include "lib/ilist.h"
+#include "replication/ddlmessage.h"
+#include "tcop/ddl_deparse.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+
+extern EventTriggerQueryState *currentEventTriggerState;
+
+/*
+ * Deparse the ddl command and log it prior to
+ * execution. Currently only used for DROP TABLE command
+ * so that catalog can be accessed before being deleted.
+ * This is to check if the table is part of the publication
+ * or not.
+ */
+Datum
+publication_deparse_ddl_command_start(PG_FUNCTION_ARGS)
+{
+ EventTriggerData *trigdata;
+ char *command = psprintf("Drop table command start");
+ DropStmt *stmt;
+ ListCell *cell1;
+
+ if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+ elog(ERROR, "not fired by event trigger manager");
+
+ trigdata = (EventTriggerData *) fcinfo->context;
+ stmt = (DropStmt *) trigdata->parsetree;
+
+ /* Extract the relid from the parse tree */
+ foreach(cell1, stmt->objects)
+ {
+ char relpersist;
+ Node *object = lfirst(cell1);
+ ObjectAddress address;
+ Relation relation = NULL;
+
+ address = get_object_address(stmt->removeType,
+ object,
+ &relation,
+ AccessExclusiveLock,
+ true);
+
+ /* Object does not exist, nothing to do */
+ if (!relation)
+ continue;
+
+ relpersist = get_rel_persistence(address.objectId);
+
+ /*
+ * Do not generate wal log for commands whose target table is a
+ * temporary or unlogged table.
+ *
+ * XXX We may generate wal logs for unlogged tables in the future so
+ * that unlogged tables can also be created and altered on the
+ * subscriber side. This makes it possible to directly replay the SET
+ * LOGGED command and the incoming rewrite message without creating a
+ * new table.
+ */
+ if (relpersist == RELPERSISTENCE_PERMANENT)
+ LogLogicalDDLMessage("deparse", address.objectId, DCT_TableDropStart,
+ command, strlen(command) + 1);
+
+ if (relation)
+ table_close(relation, NoLock);
+ }
+ return PointerGetDatum(NULL);
+}
+
+/*
+ * publication_deparse_table_rewrite
+ *
+ * Deparse the ddl table rewrite command and log it.
+ */
+Datum
+publication_deparse_table_rewrite(PG_FUNCTION_ARGS)
+{
+ char relpersist;
+ CollectedCommand *cmd;
+ char *json_string;
+
+ if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+ elog(ERROR, "not fired by event trigger manager");
+
+ cmd = currentEventTriggerState->currentCommand;
+
+ Assert(cmd && cmd->d.alterTable.rewrite);
+
+ relpersist = get_rel_persistence(cmd->d.alterTable.objectId);
+
+ /*
+ * Do not generate wal log for commands whose target table is a temporary
+ * or unlogged table.
+ *
+ * XXX We may generate wal logs for unlogged tables in the future so that
+ * unlogged tables can also be created and altered on the subscriber side.
+ * This makes it possible to directly replay the SET LOGGED command and the
+ * incoming rewrite message without creating a new table.
+ */
+ if (relpersist == RELPERSISTENCE_PERMANENT)
+ {
+ /* Deparse the DDL command and WAL log it to allow decoding of the same. */
+ json_string = deparse_utility_command(cmd, false);
+
+ if (json_string != NULL)
+ LogLogicalDDLMessage("deparse", cmd->d.alterTable.objectId, DCT_TableAlter,
+ json_string, strlen(json_string) + 1);
+ }
+
+ return PointerGetDatum(NULL);
+}
+
+/*
+ * Deparse the ddl command and log it. This function
+ * is called after the execution of the command but before the
+ * transaction commits.
+ */
+Datum
+publication_deparse_ddl_command_end(PG_FUNCTION_ARGS)
+{
+ ListCell *lc;
+ slist_iter iter;
+ DeparsedCommandType type;
+ Oid relid;
+ char relkind;
+
+ if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+ elog(ERROR, "not fired by event trigger manager");
+
+ foreach(lc, currentEventTriggerState->commandList)
+ {
+ char relpersist = RELPERSISTENCE_PERMANENT;
+ CollectedCommand *cmd = lfirst(lc);
+ char *json_string;
+
+ /* Rewrite DDL has been handled in table_rewrite trigger */
+ if (cmd->d.alterTable.rewrite)
+ {
+ RenameStmt *renameStmt = (RenameStmt *) cmd->parsetree;
+
+ if (renameStmt && renameStmt->relationType != OBJECT_TYPE &&
+ renameStmt->relationType != OBJECT_TABLE)
+ continue;
+ }
+
+ if (cmd->type == SCT_Simple &&
+ !OidIsValid(cmd->d.simple.address.objectId))
+ continue;
+
+ if (cmd->type == SCT_AlterTable)
+ {
+ relid = cmd->d.alterTable.objectId;
+ type = DCT_TableAlter;
+ }
+ else
+ {
+ /* Only SCT_Simple for now */
+ relid = cmd->d.simple.address.objectId;
+ type = DCT_SimpleCmd;
+ }
+
+ relkind = get_rel_relkind(relid);
+ if (relkind)
+ relpersist = get_rel_persistence(relid);
+
+ /*
+ * Do not generate wal log for commands whose target table is a
+ * temporary or unlogged table.
+ *
+ * XXX We may generate wal logs for unlogged tables in the future so
+ * that unlogged tables can also be created and altered on the
+ * subscriber side. This makes it possible to directly replay the SET
+ * LOGGED command and the incoming rewrite message without creating a
+ * new table.
+ */
+ if (relpersist == RELPERSISTENCE_PERMANENT)
+ {
+ /*
+ * Deparse the DDL command and WAL log it to allow decoding of the
+ * same.
+ */
+ json_string = deparse_utility_command(cmd, false);
+
+ if (json_string != NULL)
+ LogLogicalDDLMessage("deparse", relid, type, json_string,
+ strlen(json_string) + 1);
+ }
+ }
+
+ slist_foreach(iter, &(currentEventTriggerState->SQLDropList))
+ {
+ SQLDropObject *obj;
+ DropStmt *stmt;
+ EventTriggerData *trigdata;
+ char *command;
+ DeparsedCommandType cmdtype;
+
+ trigdata = (EventTriggerData *) fcinfo->context;
+ stmt = (DropStmt *) trigdata->parsetree;
+
+ obj = slist_container(SQLDropObject, next, iter.cur);
+
+ if (strcmp(obj->objecttype, "table") == 0)
+ cmdtype = DCT_TableDropEnd;
+ else
+ continue;
+
+ command = deparse_drop_command(obj->objidentity, obj->objecttype,
+ stmt->behavior);
+
+ if (command)
+ LogLogicalDDLMessage("deparse", obj->address.objectId, cmdtype,
+ command, strlen(command) + 1);
+ }
+
+ return PointerGetDatum(NULL);
+}
+
+
+/*
+ * publication_deparse_table_init_write
+ *
+ * Deparse the ddl table create command and log it.
+ */
+Datum
+publication_deparse_table_init_write(PG_FUNCTION_ARGS)
+{
+ char relpersist;
+ CollectedCommand *cmd;
+ char *json_string;
+
+ if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+ elog(ERROR, "not fired by event trigger manager");
+
+ cmd = currentEventTriggerState->currentCommand;
+ Assert(cmd);
+
+ relpersist = get_rel_persistence(cmd->d.simple.address.objectId);
+
+ /*
+ * Do not generate wal log for commands whose target table is a temporary
+ * table.
+ *
+ * We will generate wal logs for unlogged tables so that unlogged tables
+ * can also be created and altered on the subscriber side. This makes it
+ * possible to directly replay the SET LOGGED command and the incoming
+ * rewrite message without creating a new table.
+ */
+ if (relpersist != RELPERSISTENCE_PERMANENT)
+ return PointerGetDatum(NULL);
+
+ /* Deparse the DDL command and WAL log it to allow decoding of the same. */
+ json_string = deparse_utility_command(cmd, false);
+
+ if (json_string != NULL)
+ LogLogicalDDLMessage("deparse", cmd->d.simple.address.objectId, DCT_SimpleCmd,
+ json_string, strlen(json_string) + 1);
+
+ return PointerGetDatum(NULL);
+}
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 8fe7bb65f1..c615a23fe5 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -36,6 +36,7 @@
#include "access/xlogutils.h"
#include "catalog/pg_control.h"
#include "replication/decode.h"
+#include "replication/ddlmessage.h"
#include "replication/logical.h"
#include "replication/message.h"
#include "replication/origin.h"
@@ -613,6 +614,46 @@ logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
message->message + message->prefix_size);
}
+/*
+ * Handle rmgr LOGICALDDLMSG_ID records for DecodeRecordIntoReorderBuffer().
+ */
+void
+logicalddl_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+ SnapBuild *builder = ctx->snapshot_builder;
+ XLogReaderState *r = buf->record;
+ TransactionId xid = XLogRecGetXid(r);
+ uint8 info = XLogRecGetInfo(r) & ~XLR_INFO_MASK;
+ RepOriginId origin_id = XLogRecGetOrigin(r);
+ xl_logical_ddl_message *message;
+
+ if (info != XLOG_LOGICAL_DDL_MESSAGE)
+ elog(ERROR, "unexpected RM_LOGICALDDLMSG_ID record type: %u", info);
+
+ ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(r), buf->origptr);
+
+ /*
+ * If we don't have snapshot or we are just fast-forwarding, there is no
+ * point in decoding ddl messages.
+ */
+ if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT ||
+ ctx->fast_forward)
+ return;
+
+ message = (xl_logical_ddl_message *) XLogRecGetData(r);
+
+ if (message->dbId != ctx->slot->data.database ||
+ FilterByOrigin(ctx, origin_id))
+ return;
+
+ if (SnapBuildProcessChange(builder, xid, buf->origptr))
+ ReorderBufferQueueDDLMessage(ctx->reorder, xid, buf->endptr,
+ message->message, /* first part of message is prefix */
+ message->message_size,
+ message->message + message->prefix_size,
+ message->relid, message->cmdtype);
+}
+
/*
* Consolidated commit record handling between the different form of commit
* records.
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index c3ec97a0a6..f56a716567 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -33,6 +33,7 @@
#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "replication/ddlmessage.h"
#include "replication/decode.h"
#include "replication/logical.h"
#include "replication/origin.h"
@@ -73,6 +74,10 @@ static void truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
static void message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
XLogRecPtr message_lsn, bool transactional,
const char *prefix, Size message_size, const char *message);
+static void ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn, const char *prefix,
+ Oid relid, DeparsedCommandType cmdtype,
+ Size message_size, const char *message);
/* streaming callbacks */
static void stream_start_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
@@ -90,6 +95,11 @@ static void stream_change_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn
static void stream_message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
XLogRecPtr message_lsn, bool transactional,
const char *prefix, Size message_size, const char *message);
+static void stream_ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid, DeparsedCommandType cmdtype,
+ Size message_size, const char *message);
static void stream_truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
int nrelations, Relation relations[], ReorderBufferChange *change);
@@ -223,6 +233,7 @@ StartupDecodingContext(List *output_plugin_options,
ctx->reorder->apply_truncate = truncate_cb_wrapper;
ctx->reorder->commit = commit_cb_wrapper;
ctx->reorder->message = message_cb_wrapper;
+ ctx->reorder->ddl = ddl_cb_wrapper;
/*
* To support streaming, we require start/stop/abort/commit/change
@@ -239,6 +250,7 @@ StartupDecodingContext(List *output_plugin_options,
(ctx->callbacks.stream_commit_cb != NULL) ||
(ctx->callbacks.stream_change_cb != NULL) ||
(ctx->callbacks.stream_message_cb != NULL) ||
+ (ctx->callbacks.stream_ddl_cb != NULL) ||
(ctx->callbacks.stream_truncate_cb != NULL);
/*
@@ -256,6 +268,7 @@ StartupDecodingContext(List *output_plugin_options,
ctx->reorder->stream_commit = stream_commit_cb_wrapper;
ctx->reorder->stream_change = stream_change_cb_wrapper;
ctx->reorder->stream_message = stream_message_cb_wrapper;
+ ctx->reorder->stream_ddl = stream_ddl_cb_wrapper;
ctx->reorder->stream_truncate = stream_truncate_cb_wrapper;
@@ -1233,6 +1246,44 @@ message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
error_context_stack = errcallback.previous;
}
+static void
+ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message)
+{
+ LogicalDecodingContext *ctx = cache->private_data;
+ LogicalErrorCallbackState state;
+ ErrorContextCallback errcallback;
+
+ Assert(!ctx->fast_forward);
+
+ if (ctx->callbacks.ddl_cb == NULL)
+ return;
+
+ /* Push callback + info on the error context stack */
+ state.ctx = ctx;
+ state.callback_name = "ddl";
+ state.report_location = message_lsn;
+ errcallback.callback = output_plugin_error_callback;
+ errcallback.arg = (void *) &state;
+ errcallback.previous = error_context_stack;
+ error_context_stack = &errcallback;
+
+ /* set output state */
+ ctx->accept_writes = true;
+ ctx->write_xid = txn != NULL ? txn->xid : InvalidTransactionId;
+ ctx->write_location = message_lsn;
+
+ /* do the actual work: call callback */
+ ctx->callbacks.ddl_cb(ctx, txn, message_lsn, prefix, relid, cmdtype,
+ message_size, message);
+
+ /* Pop the error context stack */
+ error_context_stack = errcallback.previous;
+}
+
static void
stream_start_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
XLogRecPtr first_lsn)
@@ -1548,6 +1599,48 @@ stream_message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
error_context_stack = errcallback.previous;
}
+static void
+stream_ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message)
+{
+ LogicalDecodingContext *ctx = cache->private_data;
+ LogicalErrorCallbackState state;
+ ErrorContextCallback errcallback;
+
+ Assert(!ctx->fast_forward);
+
+ /* We're only supposed to call this when streaming is supported. */
+ Assert(ctx->streaming);
+
+ /* this callback is optional */
+ if (ctx->callbacks.stream_ddl_cb == NULL)
+ return;
+
+ /* Push callback + info on the error context stack */
+ state.ctx = ctx;
+ state.callback_name = "stream_ddl";
+ state.report_location = message_lsn;
+ errcallback.callback = output_plugin_error_callback;
+ errcallback.arg = (void *) &state;
+ errcallback.previous = error_context_stack;
+ error_context_stack = &errcallback;
+
+ /* set output state */
+ ctx->accept_writes = true;
+ ctx->write_xid = txn != NULL ? txn->xid : InvalidTransactionId;
+ ctx->write_location = message_lsn;
+
+ /* do the actual work: call callback */
+ ctx->callbacks.stream_ddl_cb(ctx, txn, message_lsn, prefix, relid,
+ cmdtype, message_size, message);
+
+ /* Pop the error context stack */
+ error_context_stack = errcallback.previous;
+}
+
static void
stream_truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
int nrelations, Relation relations[],
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index fa1b641a2b..92f5f9357e 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -29,6 +29,7 @@
#include "nodes/makefuncs.h"
#include "replication/decode.h"
#include "replication/logical.h"
+#include "replication/ddlmessage.h"
#include "replication/message.h"
#include "storage/fd.h"
#include "utils/array.h"
@@ -388,3 +389,26 @@ pg_logical_emit_message_text(PG_FUNCTION_ARGS)
/* bytea and text are compatible */
return pg_logical_emit_message_bytea(fcinfo);
}
+
+/*
+ * SQL function for writing logical decoding DDL message into WAL.
+ */
+Datum
+pg_logical_emit_ddl_message_bytea(PG_FUNCTION_ARGS)
+{
+ char *prefix = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ Oid relid = PG_GETARG_OID(1);
+ DeparsedCommandType cmdtype = PG_GETARG_INT16(2);
+ char *data = text_to_cstring(PG_GETARG_TEXT_PP(3));
+ XLogRecPtr lsn;
+
+ lsn = LogLogicalDDLMessage(prefix, relid, cmdtype, data, strlen(data));
+ PG_RETURN_LSN(lsn);
+}
+
+Datum
+pg_logical_emit_ddl_message_text(PG_FUNCTION_ARGS)
+{
+ /* bytea and text are compatible */
+ return pg_logical_emit_ddl_message_bytea(fcinfo);
+}
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..05e775697e 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -2,6 +2,8 @@
backend_sources += files(
'applyparallelworker.c',
+ 'ddlmessage.c',
+ 'ddltrigger.c',
'decode.c',
'launcher.c',
'logical.c',
diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
index f308713275..1ba3feb187 100644
--- a/src/backend/replication/logical/proto.c
+++ b/src/backend/replication/logical/proto.c
@@ -663,6 +663,47 @@ logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn,
pq_sendbytes(out, message, sz);
}
+/*
+ * Read DDL MESSAGE from stream
+ */
+char *
+logicalrep_read_ddl(StringInfo in, XLogRecPtr *lsn,
+ const char **prefix,
+ Size *sz)
+{
+ uint8 flags;
+ char *msg;
+
+ flags = pq_getmsgint(in, 1);
+ if (flags != 0)
+ elog(ERROR, "unrecognized flags %u in ddl message", flags);
+
+ *lsn = pq_getmsgint64(in);
+ *prefix = pq_getmsgstring(in);
+ *sz = pq_getmsgint(in, 4);
+ msg = (char *) pq_getmsgbytes(in, *sz);
+
+ return msg;
+}
+
+/*
+ * Write DDL MESSAGE to stream
+ */
+void
+logicalrep_write_ddl(StringInfo out, XLogRecPtr lsn,
+ const char *prefix, Size sz, const char *message)
+{
+ uint8 flags = 0;
+
+ pq_sendbyte(out, LOGICAL_REP_MSG_DDL);
+
+ pq_sendint8(out, flags);
+ pq_sendint64(out, lsn);
+ pq_sendstring(out, prefix);
+ pq_sendint32(out, sz);
+ pq_sendbytes(out, message, sz);
+}
+
/*
* Write relation description to the output stream.
*/
@@ -1238,6 +1279,8 @@ logicalrep_message_type(LogicalRepMsgType action)
return "TYPE";
case LOGICAL_REP_MSG_MESSAGE:
return "MESSAGE";
+ case LOGICAL_REP_MSG_DDL:
+ return "DDL";
case LOGICAL_REP_MSG_BEGIN_PREPARE:
return "BEGIN PREPARE";
case LOGICAL_REP_MSG_PREPARE:
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 9f44974473..e88eb2f42b 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -94,6 +94,7 @@
#include "lib/binaryheap.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "replication/ddlmessage.h"
#include "replication/logical.h"
#include "replication/reorderbuffer.h"
#include "replication/slot.h"
@@ -516,6 +517,14 @@ ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change,
pfree(change->data.msg.message);
change->data.msg.message = NULL;
break;
+ case REORDER_BUFFER_CHANGE_DDL:
+ if (change->data.ddl.prefix != NULL)
+ pfree(change->data.ddl.prefix);
+ change->data.ddl.prefix = NULL;
+ if (change->data.ddl.message != NULL)
+ pfree(change->data.ddl.message);
+ change->data.ddl.message = NULL;
+ break;
case REORDER_BUFFER_CHANGE_INVALIDATION:
if (change->data.inval.invalidations)
pfree(change->data.inval.invalidations);
@@ -895,6 +904,36 @@ ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
}
}
+/*
+ * A transactional DDL message is queued to be processed upon commit.
+ */
+void
+ReorderBufferQueueDDLMessage(ReorderBuffer *rb, TransactionId xid,
+ XLogRecPtr lsn, const char *prefix,
+ Size message_size, const char *message,
+ Oid relid, DeparsedCommandType cmdtype)
+{
+ MemoryContext oldcontext;
+ ReorderBufferChange *change;
+
+ Assert(TransactionIdIsValid(xid));
+
+ oldcontext = MemoryContextSwitchTo(rb->context);
+
+ change = ReorderBufferGetChange(rb);
+ change->action = REORDER_BUFFER_CHANGE_DDL;
+ change->data.ddl.prefix = pstrdup(prefix);
+ change->data.ddl.relid = relid;
+ change->data.ddl.cmdtype = cmdtype;
+ change->data.ddl.message_size = message_size;
+ change->data.ddl.message = palloc(message_size);
+ memcpy(change->data.ddl.message, message, message_size);
+
+ ReorderBufferQueueChange(rb, xid, lsn, change, false);
+
+ MemoryContextSwitchTo(oldcontext);
+}
+
/*
* AssertTXNLsnOrder
* Verify LSN ordering of transaction lists in the reorderbuffer
@@ -1997,6 +2036,29 @@ ReorderBufferApplyMessage(ReorderBuffer *rb, ReorderBufferTXN *txn,
change->data.msg.message);
}
+/*
+ * Helper function for ReorderBufferProcessTXN for applying the DDL message.
+ */
+static inline void
+ReorderBufferApplyDDLMessage(ReorderBuffer *rb, ReorderBufferTXN *txn,
+ ReorderBufferChange *change, bool streaming)
+{
+ if (streaming)
+ rb->stream_ddl(rb, txn, change->lsn,
+ change->data.ddl.prefix,
+ change->data.ddl.relid,
+ change->data.ddl.cmdtype,
+ change->data.ddl.message_size,
+ change->data.ddl.message);
+ else
+ rb->ddl(rb, txn, change->lsn,
+ change->data.ddl.prefix,
+ change->data.ddl.relid,
+ change->data.ddl.cmdtype,
+ change->data.ddl.message_size,
+ change->data.ddl.message);
+}
+
/*
* Function to store the command id and snapshot at the end of the current
* stream so that we can reuse the same while sending the next stream.
@@ -2379,6 +2441,10 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
ReorderBufferApplyMessage(rb, txn, change, streaming);
break;
+ case REORDER_BUFFER_CHANGE_DDL:
+ ReorderBufferApplyDDLMessage(rb, txn, change, streaming);
+ break;
+
case REORDER_BUFFER_CHANGE_INVALIDATION:
/* Execute the invalidation messages locally */
ReorderBufferExecuteInvalidations(change->data.inval.ninvalidations,
@@ -3835,6 +3901,39 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
change->data.msg.message_size);
data += change->data.msg.message_size;
+ break;
+ }
+ case REORDER_BUFFER_CHANGE_DDL:
+ {
+ char *data;
+ Size prefix_size = strlen(change->data.ddl.prefix) + 1;
+
+ sz += prefix_size + change->data.ddl.message_size +
+ sizeof(Size) + sizeof(Oid) + sizeof(int) + sizeof(Size);
+ ReorderBufferSerializeReserve(rb, sz);
+
+ data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
+
+ /* might have been reallocated above */
+ ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+ /* write the prefix, relid and cmdtype including the size */
+ memcpy(data, &prefix_size, sizeof(Size));
+ data += sizeof(Size);
+ memcpy(data, &change->data.ddl.relid, sizeof(Oid));
+ data += sizeof(Oid);
+ memcpy(data, &change->data.ddl.cmdtype, sizeof(DeparsedCommandType));
+ data += sizeof(int);
+ memcpy(data, change->data.ddl.prefix, prefix_size);
+ data += prefix_size;
+
+ /* write the message including the size */
+ memcpy(data, &change->data.ddl.message_size, sizeof(Size));
+ data += sizeof(Size);
+ memcpy(data, change->data.ddl.message,
+ change->data.ddl.message_size);
+ data += change->data.ddl.message_size;
+
break;
}
case REORDER_BUFFER_CHANGE_INVALIDATION:
@@ -4149,6 +4248,15 @@ ReorderBufferChangeSize(ReorderBufferChange *change)
sz += prefix_size + change->data.msg.message_size +
sizeof(Size) + sizeof(Size);
+ break;
+ }
+ case REORDER_BUFFER_CHANGE_DDL:
+ {
+ Size prefix_size = strlen(change->data.ddl.prefix) + 1;
+
+ sz += prefix_size + change->data.ddl.message_size +
+ sizeof(Size) + sizeof(Size) + sizeof(Oid) + sizeof(DeparsedCommandType);
+
break;
}
case REORDER_BUFFER_CHANGE_INVALIDATION:
@@ -4426,6 +4534,33 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
change->data.msg.message_size);
data += change->data.msg.message_size;
+ break;
+ }
+ case REORDER_BUFFER_CHANGE_DDL:
+ {
+ Size prefix_size;
+
+ /* read prefix */
+ memcpy(&prefix_size, data, sizeof(Size));
+ data += sizeof(Size);
+ memcpy(&change->data.ddl.relid, data, sizeof(Oid));
+ data += sizeof(Oid);
+ memcpy(&change->data.ddl.cmdtype, data, sizeof(DeparsedCommandType));
+ data += sizeof(int);
+ change->data.ddl.prefix = MemoryContextAlloc(rb->context, prefix_size);
+ memcpy(change->data.ddl.prefix, data, prefix_size);
+ Assert(change->data.ddl.prefix[prefix_size - 1] == '\0');
+ data += prefix_size;
+
+ /* read the message */
+ memcpy(&change->data.msg.message_size, data, sizeof(Size));
+ data += sizeof(Size);
+ change->data.msg.message = MemoryContextAlloc(rb->context,
+ change->data.msg.message_size);
+ memcpy(change->data.msg.message, data,
+ change->data.msg.message_size);
+ data += change->data.msg.message_size;
+
break;
}
case REORDER_BUFFER_CHANGE_INVALIDATION:
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 3d58910c14..db379e49fc 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -166,6 +166,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
@@ -191,7 +192,10 @@
#include "storage/lmgr.h"
#include "storage/proc.h"
#include "storage/procarray.h"
+#include "tcop/ddl_deparse.h"
+#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
+#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
@@ -3274,6 +3278,238 @@ apply_handle_truncate(StringInfo s)
end_replication_step();
}
+/*
+ * Special handling for CREATE TABLE AS and SELECT INTO
+ * to not populate data from the source table on the subscriber.
+ * Allow the data to be replicated through INSERTs on the publisher.
+ */
+static void
+preprocess_create_table(RawStmt *command)
+{
+ CommandTag commandTag;
+
+ commandTag = CreateCommandTag((Node *) command);
+
+ switch (commandTag)
+ {
+ case CMDTAG_CREATE_TABLE_AS:
+ case CMDTAG_SELECT_INTO:
+ {
+ CreateTableAsStmt *castmt = (CreateTableAsStmt *) command->stmt;
+
+ if (castmt->objtype == OBJECT_TABLE)
+ {
+ /*
+ * Force skipping data population to avoid data
+ * inconsistency. Data should be replicated from the
+ * publisher instead.
+ */
+ castmt->into->skipData = true;
+ }
+ }
+ break;
+ case CMDTAG_SELECT:
+ {
+ SelectStmt *sstmt = (SelectStmt *) command->stmt;
+
+ if (sstmt->intoClause != NULL)
+ {
+ /*
+ * Force skipping data population to avoid data
+ * inconsistency. Data should be replicated from the
+ * publisher instead.
+ */
+ sstmt->intoClause->skipData = true;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Handle CREATE TABLE command
+ *
+ * Call AddSubscriptionRelState for CREATE LABEL command to set the relstate to
+ * SUBREL_STATE_READY so DML changes on this new table can be replicated without
+ * having to manually run "alter subscription ... refresh publication"
+ */
+static void
+handle_create_table(RawStmt *command)
+{
+ CommandTag commandTag;
+ RangeVar *rv = NULL;
+ Oid relid;
+ Oid relnamespace = InvalidOid;
+ CreateStmt *cstmt;
+ char *schemaname = NULL;
+ char *relname = NULL;
+
+ commandTag = CreateCommandTag((Node *) command);
+ cstmt = (CreateStmt *) command->stmt;
+ rv = cstmt->relation;
+
+ if (commandTag == CMDTAG_CREATE_TABLE)
+ {
+ cstmt = (CreateStmt *) command->stmt;
+ rv = cstmt->relation;
+ }
+ else
+ {
+ return;
+ }
+
+ if (!rv)
+ return;
+
+ schemaname = rv->schemaname;
+ relname = rv->relname;
+
+ if (schemaname != NULL)
+ relnamespace = get_namespace_oid(schemaname, false);
+
+ if (relnamespace != InvalidOid)
+ relid = get_relname_relid(relname, relnamespace);
+ else
+ relid = RelnameGetRelid(relname);
+
+ if (OidIsValid(relid))
+ {
+ AddSubscriptionRelState(MySubscription->oid, relid,
+ SUBREL_STATE_READY,
+ InvalidXLogRecPtr);
+ ereport(DEBUG1,
+ (errmsg_internal("table \"%s\" added to subscription \"%s\"",
+ relname, MySubscription->name)));
+ }
+}
+
+/*
+ * Handle DDL replication messages.
+ */
+static void
+apply_handle_ddl(StringInfo s)
+{
+ XLogRecPtr lsn;
+ const char *prefix = NULL;
+ char *message = NULL;
+ char *ddl_command;
+ Size sz;
+ List *parsetree_list;
+ ListCell *parsetree_item;
+ DestReceiver *receiver;
+ MemoryContext oldcontext;
+ const char *save_debug_query_string = debug_query_string;
+
+ message = logicalrep_read_ddl(s, &lsn, &prefix, &sz);
+
+ /* Make sure we are in a transaction command */
+ begin_replication_step();
+
+ ddl_command = deparse_ddl_json_to_string(message);
+ debug_query_string = ddl_command;
+
+ /* DestNone for logical replication */
+ receiver = CreateDestReceiver(DestNone);
+ parsetree_list = pg_parse_query(ddl_command);
+
+ foreach(parsetree_item, parsetree_list)
+ {
+ List *plantree_list;
+ List *querytree_list;
+ RawStmt *command = (RawStmt *) lfirst(parsetree_item);
+ CommandTag commandTag;
+ MemoryContext per_parsetree_context = NULL;
+ Portal portal;
+ bool snapshot_set = false;
+
+ commandTag = CreateCommandTag((Node *) command);
+
+ /* If we got a cancel signal in parsing or prior command, quit */
+ CHECK_FOR_INTERRUPTS();
+
+ /* Remove data population from the command */
+ preprocess_create_table(command);
+
+ /*
+ * Set up a snapshot if parse analysis/planning will need one.
+ */
+ if (analyze_requires_snapshot(command))
+ {
+ PushActiveSnapshot(GetTransactionSnapshot());
+ snapshot_set = true;
+ }
+
+ /*
+ * We do the work for each parsetree in a short-lived context, to
+ * limit the memory used when there are many commands in the string.
+ */
+ per_parsetree_context =
+ AllocSetContextCreate(CurrentMemoryContext,
+ "execute_sql_string per-statement context",
+ ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(per_parsetree_context);
+
+ querytree_list = pg_analyze_and_rewrite_fixedparams(command,
+ ddl_command,
+ NULL, 0, NULL);
+
+ plantree_list = pg_plan_queries(querytree_list, ddl_command, 0, NULL);
+
+ /* Done with the snapshot used for parsing/planning */
+ if (snapshot_set)
+ PopActiveSnapshot();
+
+ portal = CreatePortal("logical replication", true, true);
+
+ /*
+ * We don't have to copy anything into the portal, because everything
+ * we are passing here is in ApplyMessageContext or the
+ * per_parsetree_context, and so will outlive the portal anyway.
+ */
+ PortalDefineQuery(portal,
+ NULL,
+ ddl_command,
+ commandTag,
+ plantree_list,
+ NULL);
+
+ /*
+ * Start the portal. No parameters here.
+ */
+ PortalStart(portal, NULL, 0, InvalidSnapshot);
+
+ /*
+ * Switch back to transaction context for execution.
+ */
+ MemoryContextSwitchTo(oldcontext);
+
+ (void) PortalRun(portal,
+ FETCH_ALL,
+ true,
+ true,
+ receiver,
+ receiver,
+ NULL);
+
+ PortalDrop(portal, false);
+
+ CommandCounterIncrement();
+
+ /*
+ * Table created by DDL replication (database level) is automatically
+ * added to the subscription here.
+ */
+ handle_create_table(command);
+
+ /* Now we may drop the per-parsetree context, if one was created. */
+ MemoryContextDelete(per_parsetree_context);
+ }
+
+ debug_query_string = save_debug_query_string;
+ end_replication_step();
+}
/*
* Logical replication protocol message dispatcher.
@@ -3339,6 +3575,10 @@ apply_dispatch(StringInfo s)
*/
break;
+ case LOGICAL_REP_MSG_DDL:
+ apply_handle_ddl(s);
+ break;
+
case LOGICAL_REP_MSG_STREAM_START:
apply_handle_stream_start(s);
break;
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index ebaf555d56..7ea9ff51ec 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -18,6 +18,7 @@
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_subscription.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "commands/subscriptioncmds.h"
#include "executor/executor.h"
#include "fmgr.h"
@@ -55,6 +56,11 @@ static void pgoutput_message(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn, XLogRecPtr message_lsn,
bool transactional, const char *prefix,
Size sz, const char *message);
+static void pgoutput_ddl(LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn, XLogRecPtr message_lsn,
+ const char *prefix, Oid relid,
+ DeparsedCommandType cmdtype,
+ Size sz, const char *message);
static bool pgoutput_origin_filter(LogicalDecodingContext *ctx,
RepOriginId origin_id);
static void pgoutput_begin_prepare_txn(LogicalDecodingContext *ctx,
@@ -207,6 +213,7 @@ typedef struct RelationSyncEntry
typedef struct PGOutputTxnData
{
bool sent_begin_txn; /* flag indicating whether BEGIN has been sent */
+ List *deleted_relids;
} PGOutputTxnData;
/* Map used to remember which relation schemas we sent. */
@@ -254,6 +261,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
cb->change_cb = pgoutput_change;
cb->truncate_cb = pgoutput_truncate;
cb->message_cb = pgoutput_message;
+ cb->ddl_cb = pgoutput_ddl;
cb->commit_cb = pgoutput_commit_txn;
cb->begin_prepare_cb = pgoutput_begin_prepare_txn;
@@ -270,6 +278,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
cb->stream_commit_cb = pgoutput_stream_commit;
cb->stream_change_cb = pgoutput_change;
cb->stream_message_cb = pgoutput_message;
+ cb->stream_ddl_cb = pgoutput_ddl;
cb->stream_truncate_cb = pgoutput_truncate;
/* transaction streaming - two-phase commit */
cb->stream_prepare_cb = pgoutput_stream_prepare_txn;
@@ -425,6 +434,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
/* This plugin uses binary protocol. */
opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT;
+ opt->receive_rewrites = true;
/*
* This is replication start and not slot initialization.
@@ -505,6 +515,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
/* Init publication state. */
data->publications = NIL;
+ data->deleted_relids = NIL;
publications_valid = false;
/*
@@ -533,6 +544,34 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
}
}
+/* Initialize the per-transaction level variable for the given transaction. */
+static void
+init_txn_data(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
+{
+ PGOutputTxnData *txndata;
+
+ if (txn->output_plugin_private != NULL)
+ return;
+
+ txndata = MemoryContextAllocZero(ctx->context, sizeof(PGOutputTxnData));
+
+ txn->output_plugin_private = txndata;
+}
+
+/* Clean up the per-transaction level variable for the given transaction. */
+static void
+clean_txn_data(ReorderBufferTXN *txn)
+{
+ PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
+
+ if (txndata == NULL)
+ return;
+
+ list_free(txndata->deleted_relids);
+ pfree(txndata);
+ txn->output_plugin_private = NULL;
+}
+
/*
* BEGIN callback.
*
@@ -546,10 +585,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
static void
pgoutput_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
{
- PGOutputTxnData *txndata = MemoryContextAllocZero(ctx->context,
- sizeof(PGOutputTxnData));
-
- txn->output_plugin_private = txndata;
+ init_txn_data(ctx, txn);
}
/*
@@ -594,8 +630,7 @@ pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
*/
sent_begin_txn = txndata->sent_begin_txn;
OutputPluginUpdateProgress(ctx, !sent_begin_txn);
- pfree(txndata);
- txn->output_plugin_private = NULL;
+ clean_txn_data(txn);
if (!sent_begin_txn)
{
@@ -637,6 +672,8 @@ pgoutput_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
OutputPluginPrepareWrite(ctx, true);
logicalrep_write_prepare(ctx->out, txn, prepare_lsn);
OutputPluginWrite(ctx, true);
+
+ clean_txn_data(txn);
}
/*
@@ -1411,6 +1448,19 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
ReorderBufferChangeType action = change->action;
TupleTableSlot *old_slot = NULL;
TupleTableSlot *new_slot = NULL;
+ bool table_rewrite = false;
+
+ /*
+ * For heap rewrites, we might need to replicate them if the rewritten
+ * table publishes rewrite ddl message. So get the actual relation here
+ * and check the pubaction later.
+ */
+ if (relation->rd_rel->relrewrite)
+ {
+ table_rewrite = true;
+ relation = RelationIdGetRelation(relation->rd_rel->relrewrite);
+ targetrel = relation;
+ }
if (!is_publishable_relation(relation))
return;
@@ -1456,6 +1506,13 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
Assert(false);
}
+ /*
+ * We don't publish table rewrite change unless we publish the rewrite ddl
+ * message.
+ */
+ if (table_rewrite && !relentry->pubactions.pubddl_table)
+ return;
+
/* Avoid leaking memory by using and resetting our own context */
old = MemoryContextSwitchTo(data->context);
@@ -1503,7 +1560,8 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
* Updates could be transformed to inserts or deletes based on the results
* of the row filter for old and new tuple.
*/
- if (!pgoutput_row_filter(targetrel, old_slot, &new_slot, relentry, &action))
+ if (!table_rewrite &&
+ !pgoutput_row_filter(targetrel, old_slot, &new_slot, relentry, &action))
goto cleanup;
/*
@@ -1528,8 +1586,18 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
switch (action)
{
case REORDER_BUFFER_CHANGE_INSERT:
- logicalrep_write_insert(ctx->out, xid, targetrel, new_slot,
- data->binary, relentry->columns);
+ /*
+ * Convert the rewrite inserts to updates so that the subscriber
+ * can replay it. This is needed to make sure the data between
+ * publisher and subscriber is consistent.
+ */
+ if (table_rewrite)
+ logicalrep_write_update(ctx->out, xid, targetrel,
+ NULL, new_slot, data->binary,
+ relentry->columns);
+ else
+ logicalrep_write_insert(ctx->out, xid, targetrel, new_slot,
+ data->binary, relentry->columns);
break;
case REORDER_BUFFER_CHANGE_UPDATE:
logicalrep_write_update(ctx->out, xid, targetrel, old_slot,
@@ -1552,6 +1620,9 @@ cleanup:
ancestor = NULL;
}
+ if (table_rewrite)
+ RelationClose(relation);
+
MemoryContextSwitchTo(old);
MemoryContextReset(data->context);
}
@@ -1665,6 +1736,140 @@ pgoutput_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
OutputPluginWrite(ctx, true);
}
+/* Check if the given object is published. */
+static bool
+is_object_published(LogicalDecodingContext *ctx, Oid objid)
+{
+ Relation relation = NULL;
+ RelationSyncEntry *relentry;
+ PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
+
+ /* First check the DDL command filter. */
+ switch (get_rel_relkind(objid))
+ {
+ case RELKIND_RELATION:
+ relation = RelationIdGetRelation(objid);
+ relentry = get_rel_sync_entry(data, relation);
+ RelationClose(relation);
+
+ /*
+ * Skip sending this ddl if we don't publish ddl message or the ddl
+ * need to be published via its root relation.
+ */
+ if (!relentry->pubactions.pubddl_table ||
+ relentry->publish_as_relid != objid)
+ return false;
+
+ break;
+ default:
+ /* unsupported objects */
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Send the decoded DDL over wire.
+ */
+static void
+pgoutput_ddl(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ Size sz, const char *message)
+{
+ PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
+
+ /*
+ * Check if the given object is published. Note that for dropped objects,
+ * we cannot get the required information from the catalog, so we skip the
+ * check for them.
+ */
+ if (cmdtype != DCT_TableDropEnd && !is_object_published(ctx, relid))
+ return;
+
+ switch (cmdtype)
+ {
+ case DCT_TableDropStart:
+ {
+ MemoryContext old;
+
+ init_txn_data(ctx, txn);
+
+ txndata = (PGOutputTxnData *) txn->output_plugin_private;
+
+ /*
+ * On DROP start, add the relid to a deleted_relid list if the
+ * relid is part of a publication that supports ddl
+ * publication. We need this because on DROP end, the relid
+ * will no longer be valid. Later on Drop end, verify that the
+ * drop is for a relid that is on the deleted_rid list, and
+ * only then send the ddl message.
+ */
+ old = MemoryContextSwitchTo(ctx->context);
+ txndata->deleted_relids = lappend_oid(txndata->deleted_relids,
+ relid);
+ MemoryContextSwitchTo(old);
+ }
+ return;
+
+ case DCT_TableDropEnd:
+ if (!list_member_oid(txndata->deleted_relids, relid))
+ return;
+
+ txndata->deleted_relids = list_delete_oid(txndata->deleted_relids,
+ relid);
+ break;
+
+ case DCT_TableAlter:
+
+ /*
+ * For table rewrite ddl, we first send the original ddl message
+ * to subscriber, then convert the upcoming rewrite INSERT to
+ * UPDATE and send them to subscriber so that the data between
+ * publisher and subscriber can always be consistent.
+ *
+ * We do this way because of two reason:
+ *
+ * (1) The data before the rewrite ddl could already be different
+ * among publisher and subscriber. To make sure the extra data in
+ * subscriber which doesn't exist in publisher also get rewritten,
+ * we need to let the subscriber execute the original rewrite ddl
+ * to rewrite all the data at first.
+ *
+ * (2) the data after executing rewrite ddl could be different
+ * among publisher and subscriber(due to different
+ * functions/operators used during rewrite), so we need to
+ * replicate the rewrite UPDATEs to keep the data consistent.
+ *
+ * TO IMPROVE: We could improve this by letting the subscriber
+ * only rewrite the extra data instead of doing fully rewrite and
+ * use the upcoming rewrite UPDATEs to rewrite the rest data.
+ * Besides, we may not need to send rewrite changes for all type
+ * of rewrite ddl, for example, it seems fine to skip sending
+ * rewrite changes for ALTER TABLE SET LOGGED as the data in the
+ * table doesn't actually be changed.
+ */
+ break;
+
+ case DCT_SimpleCmd:
+ /* do nothing */
+ break;
+
+ default:
+ elog(ERROR, "unsupported type %d", cmdtype);
+ break;
+ }
+
+ /* Send BEGIN if we haven't yet */
+ if (txndata && !txndata->sent_begin_txn)
+ pgoutput_send_begin(ctx, txn);
+
+ OutputPluginPrepareWrite(ctx, true);
+ logicalrep_write_ddl(ctx->out, message_lsn, prefix, sz, message);
+ OutputPluginWrite(ctx, true);
+}
+
/*
* Return true if the data is associated with an origin and the user has
* requested the changes that don't have an origin, false otherwise.
@@ -1813,6 +2018,7 @@ pgoutput_stream_abort(struct LogicalDecodingContext *ctx,
OutputPluginWrite(ctx, true);
cleanup_rel_sync_cache(toptxn->xid, false);
+ clean_txn_data(txn);
}
/*
@@ -1838,6 +2044,7 @@ pgoutput_stream_commit(struct LogicalDecodingContext *ctx,
OutputPluginWrite(ctx, true);
cleanup_rel_sync_cache(txn->xid, true);
+ clean_txn_data(txn);
}
/*
@@ -1856,6 +2063,8 @@ pgoutput_stream_prepare_txn(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
logicalrep_write_stream_prepare(ctx->out, txn, prepare_lsn);
OutputPluginWrite(ctx, true);
+
+ clean_txn_data(txn);
}
/*
@@ -1941,6 +2150,27 @@ set_schema_sent_in_streamed_txn(RelationSyncEntry *entry, TransactionId xid)
MemoryContextSwitchTo(oldctx);
}
+/* Reload publications if needed. */
+static void
+reload_publications(PGOutputData *data)
+{
+ MemoryContext oldctx;
+
+ if (!publications_valid)
+ {
+ oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+ if (data->publications)
+ {
+ list_free_deep(data->publications);
+ data->publications = NIL;
+ }
+ data->publications = LoadPublications(data->publication_names);
+ MemoryContextSwitchTo(oldctx);
+ publications_valid = true;
+ }
+}
+
+
/*
* Find or create entry in the relation schema cache.
*
@@ -1955,7 +2185,6 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
{
RelationSyncEntry *entry;
bool found;
- MemoryContext oldctx;
Oid relid = RelationGetRelid(relation);
Assert(RelationSyncCache != NULL);
@@ -1973,7 +2202,8 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->schema_sent = false;
entry->streamed_txns = NIL;
entry->pubactions.pubinsert = entry->pubactions.pubupdate =
- entry->pubactions.pubdelete = entry->pubactions.pubtruncate = false;
+ entry->pubactions.pubdelete = entry->pubactions.pubtruncate =
+ entry->pubactions.pubddl_table = false;
entry->new_slot = NULL;
entry->old_slot = NULL;
memset(entry->exprstate, 0, sizeof(entry->exprstate));
@@ -2002,19 +2232,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
char relkind = get_rel_relkind(relid);
List *rel_publications = NIL;
- /* Reload publications if needed before use. */
- if (!publications_valid)
- {
- oldctx = MemoryContextSwitchTo(CacheMemoryContext);
- if (data->publications)
- {
- list_free_deep(data->publications);
- data->publications = NIL;
- }
- data->publications = LoadPublications(data->publication_names);
- MemoryContextSwitchTo(oldctx);
- publications_valid = true;
- }
+ reload_publications(data);
/*
* Reset schema_sent status as the relation definition may have
@@ -2031,6 +2249,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->pubactions.pubupdate = false;
entry->pubactions.pubdelete = false;
entry->pubactions.pubtruncate = false;
+ entry->pubactions.pubddl_table = false;
/*
* Tuple slots cleanups. (Will be rebuilt later if needed).
@@ -2144,6 +2363,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->pubactions.pubupdate |= pub->pubactions.pubupdate;
entry->pubactions.pubdelete |= pub->pubactions.pubdelete;
entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate;
+ entry->pubactions.pubddl_table |= pub->pubactions.pubddl_table;
/*
* We want to publish the changes as the top-most ancestor
diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c
index 4bd713a0b4..ce09c5f233 100644
--- a/src/backend/tcop/cmdtag.c
+++ b/src/backend/tcop/cmdtag.c
@@ -26,10 +26,11 @@ typedef struct CommandTagBehavior
const bool table_rewrite_ok;
const bool display_rowcount; /* should the number of rows affected be
* shown in the command completion string */
+ const bool ddl_replication_ok;
} CommandTagBehavior;
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
- { name, (uint8) (sizeof(name) - 1), evtrgok, rwrok, rowcnt },
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, ddlreplok) \
+ { name, (uint8) (sizeof(name) - 1), evtrgok, rwrok, rowcnt, ddlreplok },
static const CommandTagBehavior tag_behavior[COMMAND_TAG_NEXTTAG] = {
#include "tcop/cmdtaglist.h"
@@ -57,6 +58,21 @@ GetCommandTagNameAndLen(CommandTag commandTag, Size *len)
return tag_behavior[commandTag].name;
}
+CommandTag *
+GetCommandTagsForDDLRepl(int *ncommands)
+{
+ CommandTag *ddlrepl_commands = palloc0(COMMAND_TAG_NEXTTAG * sizeof(CommandTag));
+ *ncommands = 0;
+
+ for(int i = 0; i < COMMAND_TAG_NEXTTAG; i++)
+ {
+ if (tag_behavior[i].ddl_replication_ok)
+ ddlrepl_commands[(*ncommands)++] = (CommandTag) i;
+ }
+
+ return ddlrepl_commands;
+}
+
bool
command_tag_display_rowcount(CommandTag commandTag)
{
@@ -75,6 +91,12 @@ command_tag_table_rewrite_ok(CommandTag commandTag)
return tag_behavior[commandTag].table_rewrite_ok;
}
+bool
+command_tag_ddl_replication_ok(CommandTag commandTag)
+{
+ return tag_behavior[commandTag].ddl_replication_ok;
+}
+
/*
* Search CommandTag by name
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4501cc337c..578a229ffc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2264,6 +2264,8 @@ stringify_objtype(ObjectType objtype, bool isgrant)
return "POLICY";
case OBJECT_PROCEDURE:
return "PROCEDURE";
+ case OBJECT_PUBLICATION:
+ return "PUBLICATION";
case OBJECT_ROLE:
return "ROLE";
case OBJECT_ROUTINE:
@@ -2276,6 +2278,8 @@ stringify_objtype(ObjectType objtype, bool isgrant)
return "SEQUENCE";
case OBJECT_STATISTIC_EXT:
return "STATISTICS";
+ case OBJECT_SUBSCRIPTION:
+ return "SUBSCRIPTION";
case OBJECT_TABLE:
return "TABLE";
case OBJECT_TABLESPACE:
@@ -2304,10 +2308,8 @@ stringify_objtype(ObjectType objtype, bool isgrant)
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
case OBJECT_PARAMETER_ACL:
- case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
- case OBJECT_SUBSCRIPTION:
case OBJECT_TABCONSTRAINT:
case OBJECT_TRANSFORM:
elog(ERROR, "unsupported object type %d", objtype);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 40140de958..9198da2eb1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5720,6 +5720,7 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
pubdesc->pubactions.pubupdate |= pubform->pubupdate;
pubdesc->pubactions.pubdelete |= pubform->pubdelete;
pubdesc->pubactions.pubtruncate |= pubform->pubtruncate;
+ pubdesc->pubactions.pubddl_table |= pubform->pubddl_table;
/*
* Check if all columns referenced in the filter expression are part
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7a504dfe25..3c3a34267f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -51,6 +51,7 @@
#include "catalog/pg_largeobject_d.h"
#include "catalog/pg_largeobject_metadata_d.h"
#include "catalog/pg_proc_d.h"
+#include "catalog/pg_publication.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_trigger_d.h"
#include "catalog/pg_type_d.h"
@@ -4064,6 +4065,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubupdate;
int i_pubdelete;
int i_pubtruncate;
+ int i_pubddl_table;
int i_pubviaroot;
int i,
ntups;
@@ -4079,23 +4081,29 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 160000)
+ appendPQExpBufferStr(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "p.pubowner, "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubddl_table, p.pubviaroot "
+ "FROM pg_publication p");
+ else if (fout->remoteVersion >= 130000)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, p.pubviaroot "
"FROM pg_publication p");
else if (fout->remoteVersion >= 110000)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false AS pubviaroot "
"FROM pg_publication p");
else
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false as p.pubddl_table, false AS pubviaroot "
"FROM pg_publication p");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4111,6 +4119,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubupdate = PQfnumber(res, "pubupdate");
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
+ i_pubddl_table = PQfnumber(res, "pubddl_table");
i_pubviaroot = PQfnumber(res, "pubviaroot");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4134,6 +4143,8 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubdelete), "t") == 0);
pubinfo[i].pubtruncate =
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
+ pubinfo[i].pubddl_table =
+ (strcmp(PQgetvalue(res, i, i_pubddl_table), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
@@ -4213,7 +4224,10 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
first = false;
}
- appendPQExpBufferChar(query, '\'');
+ appendPQExpBufferStr(query, "'");
+
+ if (pubinfo->pubddl_table)
+ appendPQExpBufferStr(query, ", ddl = 'table'");
if (pubinfo->pubviaroot)
appendPQExpBufferStr(query, ", publish_via_partition_root = true");
@@ -7977,6 +7991,50 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
destroyPQExpBuffer(tbloids);
}
+/*
+ * getPublicationEventTriggers
+ * get the publication event triggers that should be skipped
+ */
+static void
+getPublicationEventTriggers(Archive *fout, SimpleStringList *skipTriggers)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ int i;
+ int ntups;
+
+ query = createPQExpBuffer();
+
+ appendPQExpBufferStr(query,
+ "SELECT oid FROM pg_publication "
+ "WHERE pubddl_table");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ ntups = PQntuples(res);
+
+ for (i = 0; i < ntups; i++)
+ {
+ char *trigname;
+ Oid pubid = atooid(PQgetvalue(res, 0, 0));
+
+ trigname = psprintf(PUB_EVENT_TRIG_PREFIX, PUB_TRIG_EVENT1, pubid);
+ simple_string_list_append(skipTriggers, trigname);
+
+ trigname = psprintf(PUB_EVENT_TRIG_PREFIX, PUB_TRIG_EVENT2, pubid);
+ simple_string_list_append(skipTriggers, trigname);
+
+ trigname = psprintf(PUB_EVENT_TRIG_PREFIX, PUB_TRIG_EVENT3, pubid);
+ simple_string_list_append(skipTriggers, trigname);
+
+ trigname = psprintf(PUB_EVENT_TRIG_PREFIX, PUB_TRIG_EVENT4, pubid);
+ simple_string_list_append(skipTriggers, trigname);
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+}
+
/*
* getEventTriggers
* get information about event triggers
@@ -7997,6 +8055,7 @@ getEventTriggers(Archive *fout, int *numEventTriggers)
i_evtfname,
i_evtenabled;
int ntups;
+ SimpleStringList skipTriggers = {NULL, NULL};
/* Before 9.3, there are no event triggers */
if (fout->remoteVersion < 90300)
@@ -8005,6 +8064,8 @@ getEventTriggers(Archive *fout, int *numEventTriggers)
return NULL;
}
+ getPublicationEventTriggers(fout, &skipTriggers);
+
query = createPQExpBuffer();
appendPQExpBufferStr(query,
@@ -8049,9 +8110,14 @@ getEventTriggers(Archive *fout, int *numEventTriggers)
evtinfo[i].evtenabled = *(PQgetvalue(res, i, i_evtenabled));
/* Decide whether we want to dump it */
- selectDumpableObject(&(evtinfo[i].dobj), fout);
+ if (simple_string_list_member(&skipTriggers, evtinfo[i].evtname))
+ evtinfo[i].dobj.dump= DUMP_COMPONENT_NONE;
+ else
+ selectDumpableObject(&(evtinfo[i].dobj), fout);
}
+ simple_string_list_destroy(&skipTriggers);
+
PQclear(res);
destroyPQExpBuffer(query);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index ed6ce41ad7..bd5f8fb669 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -620,6 +620,7 @@ typedef struct _PublicationInfo
bool pubupdate;
bool pubdelete;
bool pubtruncate;
+ bool pubddl_table;
bool pubviaroot;
} PublicationInfo;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index b5c97694e3..3bb3a4ae9b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2876,7 +2876,7 @@ my %tests = (
create_order => 50,
create_sql => 'CREATE PUBLICATION pub2
FOR ALL TABLES
- WITH (publish = \'\');',
+ WITH (publish = \'\', ddl = \'\');',
regexp => qr/^
\QCREATE PUBLICATION pub2 FOR ALL TABLES WITH (publish = '');\E
/xm,
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..daf1730252 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -26,6 +26,7 @@
#include "commands/dbcommands_xlog.h"
#include "commands/sequence.h"
#include "commands/tablespace.h"
+#include "replication/ddlmessage.h"
#include "replication/message.h"
#include "replication/origin.h"
#include "rmgrdesc.h"
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 83a37ee601..e977e6f464 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6183,7 +6183,7 @@ listPublications(const char *pattern)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
- static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
+ static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false};
if (pset.sversion < 100000)
{
@@ -6200,13 +6200,18 @@ listPublications(const char *pattern)
printfPQExpBuffer(&buf,
"SELECT pubname AS \"%s\",\n"
" pg_catalog.pg_get_userbyid(pubowner) AS \"%s\",\n"
- " puballtables AS \"%s\",\n"
- " pubinsert AS \"%s\",\n"
- " pubupdate AS \"%s\",\n"
- " pubdelete AS \"%s\"",
+ " puballtables AS \"%s\"",
gettext_noop("Name"),
gettext_noop("Owner"),
- gettext_noop("All tables"),
+ gettext_noop("All tables"));
+ if (pset.sversion >= 160000)
+ appendPQExpBuffer(&buf,
+ ",\n pubddl_table AS \"%s\"",
+ gettext_noop("Table DDLs"));
+ appendPQExpBuffer(&buf,
+ ",\n pubinsert AS \"%s\",\n"
+ " pubupdate AS \"%s\",\n"
+ " pubdelete AS \"%s\"",
gettext_noop("Inserts"),
gettext_noop("Updates"),
gettext_noop("Deletes"));
@@ -6218,7 +6223,6 @@ listPublications(const char *pattern)
appendPQExpBuffer(&buf,
",\n pubviaroot AS \"%s\"",
gettext_noop("Via root"));
-
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
@@ -6308,6 +6312,7 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubddl;
PQExpBufferData title;
printTableContent cont;
@@ -6324,13 +6329,19 @@ describePublications(const char *pattern)
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
+ has_pubddl = (pset.sversion >= 160000);
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
"SELECT oid, pubname,\n"
" pg_catalog.pg_get_userbyid(pubowner) AS owner,\n"
- " puballtables, pubinsert, pubupdate, pubdelete");
+ " puballtables");
+ if (has_pubddl)
+ appendPQExpBufferStr(&buf,
+ ", pubddl_table");
+ appendPQExpBufferStr(&buf,
+ ", pubinsert, pubupdate, pubdelete");
if (has_pubtruncate)
appendPQExpBufferStr(&buf,
", pubtruncate");
@@ -6388,6 +6399,8 @@ describePublications(const char *pattern)
ncols++;
if (has_pubviaroot)
ncols++;
+ if (has_pubddl)
+ ncols++;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6395,6 +6408,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Owner"), true, align);
printTableAddHeader(&cont, gettext_noop("All tables"), true, align);
+ if (has_pubddl)
+ printTableAddHeader(&cont, gettext_noop("Table DDLs"), true, align);
printTableAddHeader(&cont, gettext_noop("Inserts"), true, align);
printTableAddHeader(&cont, gettext_noop("Updates"), true, align);
printTableAddHeader(&cont, gettext_noop("Deletes"), true, align);
@@ -6412,6 +6427,8 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubddl)
+ printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
if (!puballtables)
{
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 463bcb67c5..abcbe97593 100644
--- a/src/include/access/rmgrlist.h
+++ b/src/include/access/rmgrlist.h
@@ -47,3 +47,4 @@ PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_i
PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL, NULL, NULL)
PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL, generic_mask, NULL)
PG_RMGR(RM_LOGICALMSG_ID, "LogicalMessage", logicalmsg_redo, logicalmsg_desc, logicalmsg_identify, NULL, NULL, NULL, logicalmsg_decode)
+PG_RMGR(RM_LOGICALDDLMSG_ID, "LogicalDDLMessage", logicalddlmsg_redo, logicalddlmsg_desc, logicalddlmsg_identify, NULL, NULL, NULL, logicalddl_decode)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 742df47381..372414af0f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11156,6 +11156,14 @@
proname => 'pg_logical_emit_message', provolatile => 'v', proparallel => 'u',
prorettype => 'pg_lsn', proargtypes => 'bool text bytea',
prosrc => 'pg_logical_emit_message_bytea' },
+{ oid => '3813', descr => 'emit a textual logical decoding message',
+ proname => 'pg_logical_emit_ddl_message', provolatile => 'v', proparallel => 'u',
+ prorettype => 'pg_lsn', proargtypes => 'bool text text',
+ prosrc => 'pg_logical_emit_ddl_message_text' },
+{ oid => '3814', descr => 'emit a binary logical decoding message',
+ proname => 'pg_logical_emit_ddl_message', provolatile => 'v', proparallel => 'u',
+ prorettype => 'pg_lsn', proargtypes => 'text regclass int4 text',
+ prosrc => 'pg_logical_emit_ddl_message_bytea' },
# event triggers
{ oid => '3566', descr => 'list objects dropped by the current command',
@@ -12059,5 +12067,17 @@
{ oid => '4643', descr => 'expand JSON format DDL to a plain text DDL command',
proname => 'ddl_deparse_expand_command', prorettype => 'text',
proargtypes => 'text', prosrc => 'ddl_deparse_expand_command' },
+{ oid => '4644', descr => 'trigger for ddl command deparse end',
+ proname => 'publication_deparse_ddl_command_end', prorettype => 'event_trigger',
+ proargtypes => '', prosrc => 'publication_deparse_ddl_command_end' },
+{ oid => '4645', descr => 'trigger for ddl command deparse start',
+ proname => 'publication_deparse_ddl_command_start', prorettype => 'event_trigger',
+ proargtypes => '', prosrc => 'publication_deparse_ddl_command_start' },
+{ oid => '4646', descr => 'trigger for ddl command deparse table rewrite',
+ proname => 'publication_deparse_table_rewrite', prorettype => 'event_trigger',
+ proargtypes => '', prosrc => 'publication_deparse_table_rewrite' },
+{ oid => '4647', descr => 'trigger for ddl command deparse table init',
+ proname => 'publication_deparse_table_init_write', prorettype => 'event_trigger',
+ proargtypes => '', prosrc => 'publication_deparse_table_init_write' },
]
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 6ecaa2a01e..74b28d7350 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,8 +18,17 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
+#include "nodes/pg_list.h"
+
+/* Publication trigger events */
+#define PUB_TRIG_EVENT1 "ddl_command_end"
+#define PUB_TRIG_EVENT2 "ddl_command_start"
+#define PUB_TRIG_EVENT3 "table_rewrite"
+#define PUB_TRIG_EVENT4 "table_init_write"
+
+/* Publication event trigger prefix */
+#define PUB_EVENT_TRIG_PREFIX "pg_deparse_trig_%s_%u"
/* ----------------
* pg_publication definition. cpp turns this into
@@ -54,6 +63,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if partition changes are published using root schema */
bool pubviaroot;
+
+ /* true if table ddls are published */
+ bool pubddl_table;
} FormData_pg_publication;
/* ----------------
@@ -72,6 +84,7 @@ typedef struct PublicationActions
bool pubupdate;
bool pubdelete;
bool pubtruncate;
+ bool pubddl_table;
} PublicationActions;
typedef struct PublicationDesc
@@ -103,13 +116,6 @@ typedef struct Publication
PublicationActions pubactions;
} Publication;
-typedef struct PublicationRelInfo
-{
- Relation relation;
- Node *whereClause;
- List *columns;
-} PublicationRelInfo;
-
extern Publication *GetPublication(Oid pubid);
extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
extern List *GetRelationPublications(Oid relid);
@@ -145,14 +151,6 @@ extern List *GetPubPartitionOptionRelations(List *result,
extern Oid GetTopMostAncestorInPublication(Oid puboid, List *ancestors,
int *ancestor_level);
-extern bool is_publishable_relation(Relation rel);
extern bool is_schema_publication(Oid pubid);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists);
-extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
- bool if_not_exists);
-
-extern Bitmapset *pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols,
- MemoryContext mcxt);
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 70d5e3680a..a3fca9e64c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -22,6 +22,13 @@
/* Same as MAXNUMMESSAGES in sinvaladt.c */
#define MAX_RELCACHE_INVAL_MSGS 4096
+typedef struct PublicationRelInfo
+{
+ Relation relation;
+ Node *whereClause;
+ List *columns;
+} PublicationRelInfo;
+
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
@@ -35,5 +42,12 @@ extern bool pub_rf_contains_invalid_column(Oid pubid, Relation relation,
List *ancestors, bool pubviaroot);
extern bool pub_collist_contains_invalid_column(Oid pubid, Relation relation,
List *ancestors, bool pubviaroot);
+extern Bitmapset *pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols,
+ MemoryContext mcxt);
+extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
+extern bool is_publishable_relation(Relation rel);
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/replication/ddlmessage.h b/src/include/replication/ddlmessage.h
new file mode 100644
index 0000000000..77df6ea11a
--- /dev/null
+++ b/src/include/replication/ddlmessage.h
@@ -0,0 +1,60 @@
+/*-------------------------------------------------------------------------
+ * ddlmessage.h
+ * Exports from replication/logical/ddlmessage.c
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * src/include/replication/ddlmessage.h
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_LOGICAL_DDL_MESSAGE_H
+#define PG_LOGICAL_DDL_MESSAGE_H
+
+#include "access/xlog.h"
+#include "access/xlogdefs.h"
+#include "access/xlogreader.h"
+#include "nodes/nodes.h"
+
+
+/*
+ * Support for keeping track of deparsed commands.
+ */
+typedef enum DeparsedCommandType
+{
+ DCT_SimpleCmd,
+ DCT_TableDropStart,
+ DCT_TableDropEnd,
+ DCT_TableAlter,
+ DCT_ObjectCreate,
+ DCT_ObjectDrop
+} DeparsedCommandType;
+
+/*
+ * Generic logical decoding DDL message wal record.
+ */
+typedef struct xl_logical_ddl_message
+{
+ Oid dbId; /* database Oid emitted from */
+ Size prefix_size; /* length of prefix including null terminator */
+ Oid relid; /* id of the table */
+ DeparsedCommandType cmdtype; /* type of sql command */
+ Size message_size; /* size of the message */
+
+ /*
+ * payload, including null-terminated prefix of length prefix_size
+ */
+ char message[FLEXIBLE_ARRAY_MEMBER];
+} xl_logical_ddl_message;
+
+#define SizeOfLogicalDDLMessage (offsetof(xl_logical_ddl_message, message))
+
+extern XLogRecPtr LogLogicalDDLMessage(const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ const char *ddl_message, size_t size);
+
+/* RMGR API*/
+#define XLOG_LOGICAL_DDL_MESSAGE 0x00
+void logicalddlmsg_redo(XLogReaderState *record);
+void logicalddlmsg_desc(StringInfo buf, XLogReaderState *record);
+const char *logicalddlmsg_identify(uint8 info);
+
+#endif
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 14fa921ab4..c9ac708d32 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -27,6 +27,7 @@ extern void heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
extern void xact_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
extern void standby_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+extern void logicalddl_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
diff --git a/src/include/replication/logicalproto.h b/src/include/replication/logicalproto.h
index 0ea2df5088..5fb3baaeb1 100644
--- a/src/include/replication/logicalproto.h
+++ b/src/include/replication/logicalproto.h
@@ -66,6 +66,7 @@ typedef enum LogicalRepMsgType
LOGICAL_REP_MSG_RELATION = 'R',
LOGICAL_REP_MSG_TYPE = 'Y',
LOGICAL_REP_MSG_MESSAGE = 'M',
+ LOGICAL_REP_MSG_DDL = 'L',
LOGICAL_REP_MSG_BEGIN_PREPARE = 'b',
LOGICAL_REP_MSG_PREPARE = 'P',
LOGICAL_REP_MSG_COMMIT_PREPARED = 'K',
@@ -246,6 +247,9 @@ extern List *logicalrep_read_truncate(StringInfo in,
bool *cascade, bool *restart_seqs);
extern void logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn,
bool transactional, const char *prefix, Size sz, const char *message);
+extern void logicalrep_write_ddl(StringInfo out, XLogRecPtr lsn,
+ const char *prefix, Size sz, const char *message);
+extern char *logicalrep_read_ddl(StringInfo in, XLogRecPtr *lsn, const char **prefix, Size *sz);
extern void logicalrep_write_rel(StringInfo out, TransactionId xid,
Relation rel, Bitmapset *columns);
extern LogicalRepRelation *logicalrep_read_rel(StringInfo in);
diff --git a/src/include/replication/output_plugin.h b/src/include/replication/output_plugin.h
index 2d89d26586..44baf40720 100644
--- a/src/include/replication/output_plugin.h
+++ b/src/include/replication/output_plugin.h
@@ -90,6 +90,18 @@ typedef void (*LogicalDecodeMessageCB) (struct LogicalDecodingContext *ctx,
Size message_size,
const char *message);
+/*
+ * Called for the logical decoding DDL messages.
+ */
+typedef void (*LogicalDecodeDDLMessageCB) (struct LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid,
+ DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message);
+
/*
* Filter changes by origin.
*/
@@ -201,6 +213,19 @@ typedef void (*LogicalDecodeStreamMessageCB) (struct LogicalDecodingContext *ctx
Size message_size,
const char *message);
+/*
+ * Callback for streaming logical decoding DDL messages from in-progress
+ * transactions.
+ */
+typedef void (*LogicalDecodeStreamDDLMessageCB) (struct LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid,
+ DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message);
+
/*
* Callback for streaming truncates from in-progress transactions.
*/
@@ -221,6 +246,7 @@ typedef struct OutputPluginCallbacks
LogicalDecodeTruncateCB truncate_cb;
LogicalDecodeCommitCB commit_cb;
LogicalDecodeMessageCB message_cb;
+ LogicalDecodeDDLMessageCB ddl_cb;
LogicalDecodeFilterByOriginCB filter_by_origin_cb;
LogicalDecodeShutdownCB shutdown_cb;
@@ -239,6 +265,7 @@ typedef struct OutputPluginCallbacks
LogicalDecodeStreamCommitCB stream_commit_cb;
LogicalDecodeStreamChangeCB stream_change_cb;
LogicalDecodeStreamMessageCB stream_message_cb;
+ LogicalDecodeStreamDDLMessageCB stream_ddl_cb;
LogicalDecodeStreamTruncateCB stream_truncate_cb;
} OutputPluginCallbacks;
diff --git a/src/include/replication/pgoutput.h b/src/include/replication/pgoutput.h
index b4a8015403..a2cf99b4e4 100644
--- a/src/include/replication/pgoutput.h
+++ b/src/include/replication/pgoutput.h
@@ -25,6 +25,7 @@ typedef struct PGOutputData
uint32 protocol_version;
List *publication_names;
List *publications;
+ List *deleted_relids;
bool binary;
char streaming;
bool messages;
diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h
index e37f5120eb..25a061ac70 100644
--- a/src/include/replication/reorderbuffer.h
+++ b/src/include/replication/reorderbuffer.h
@@ -11,6 +11,8 @@
#include "access/htup_details.h"
#include "lib/ilist.h"
+#include "nodes/nodes.h"
+#include "replication/ddlmessage.h"
#include "storage/sinval.h"
#include "utils/hsearch.h"
#include "utils/relcache.h"
@@ -65,6 +67,7 @@ typedef enum ReorderBufferChangeType
REORDER_BUFFER_CHANGE_INSERT,
REORDER_BUFFER_CHANGE_UPDATE,
REORDER_BUFFER_CHANGE_DELETE,
+ REORDER_BUFFER_CHANGE_DDL,
REORDER_BUFFER_CHANGE_MESSAGE,
REORDER_BUFFER_CHANGE_INVALIDATION,
REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT,
@@ -139,6 +142,16 @@ typedef struct ReorderBufferChange
char *message;
} msg;
+ /* DDL */
+ struct
+ {
+ char *prefix;
+ Size message_size;
+ char *message;
+ Oid relid;
+ DeparsedCommandType cmdtype;
+ } ddl;
+
/* New snapshot, set when action == *_INTERNAL_SNAPSHOT */
Snapshot snapshot;
@@ -470,6 +483,16 @@ typedef void (*ReorderBufferMessageCB) (ReorderBuffer *rb,
const char *prefix, Size sz,
const char *message);
+/* DDL message callback signature */
+typedef void (*ReorderBufferDDLMessageCB) (ReorderBuffer *rb,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid,
+ DeparsedCommandType cmdtype,
+ Size sz,
+ const char *message);
+
/* begin prepare callback signature */
typedef void (*ReorderBufferBeginPrepareCB) (ReorderBuffer *rb,
ReorderBufferTXN *txn);
@@ -536,6 +559,17 @@ typedef void (*ReorderBufferStreamMessageCB) (
const char *prefix, Size sz,
const char *message);
+/* stream DDL message callback signature */
+typedef void (*ReorderBufferStreamDDLMessageCB) (
+ ReorderBuffer *rb,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid,
+ DeparsedCommandType cmdtype,
+ Size sz,
+ const char *message);
+
/* stream truncate callback signature */
typedef void (*ReorderBufferStreamTruncateCB) (
ReorderBuffer *rb,
@@ -592,6 +626,7 @@ struct ReorderBuffer
ReorderBufferApplyTruncateCB apply_truncate;
ReorderBufferCommitCB commit;
ReorderBufferMessageCB message;
+ ReorderBufferDDLMessageCB ddl;
/*
* Callbacks to be called when streaming a transaction at prepare time.
@@ -611,6 +646,7 @@ struct ReorderBuffer
ReorderBufferStreamCommitCB stream_commit;
ReorderBufferStreamChangeCB stream_change;
ReorderBufferStreamMessageCB stream_message;
+ ReorderBufferStreamDDLMessageCB stream_ddl;
ReorderBufferStreamTruncateCB stream_truncate;
/*
@@ -696,6 +732,9 @@ extern void ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
Snapshot snap, XLogRecPtr lsn,
bool transactional, const char *prefix,
Size message_size, const char *message);
+extern void ReorderBufferQueueDDLMessage(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
+ const char *prefix, Size message_size,
+ const char *message, Oid relid, DeparsedCommandType cmdtype);
extern void ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
TimestampTz commit_time, RepOriginId origin_id, XLogRecPtr origin_lsn);
diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h
index 1e7514dcff..076c27e642 100644
--- a/src/include/tcop/cmdtag.h
+++ b/src/include/tcop/cmdtag.h
@@ -16,7 +16,7 @@
/* buffer size required for command completion tags */
#define COMPLETION_TAG_BUFSIZE 64
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, ddlreplok) \
tag,
typedef enum CommandTag
@@ -53,9 +53,11 @@ CopyQueryCompletion(QueryCompletion *dst, const QueryCompletion *src)
extern void InitializeQueryCompletion(QueryCompletion *qc);
extern const char *GetCommandTagName(CommandTag commandTag);
extern const char *GetCommandTagNameAndLen(CommandTag commandTag, Size *len);
+extern CommandTag *GetCommandTagsForDDLRepl(int *ncommands);
extern bool command_tag_display_rowcount(CommandTag commandTag);
extern bool command_tag_event_trigger_ok(CommandTag commandTag);
extern bool command_tag_table_rewrite_ok(CommandTag commandTag);
+extern bool command_tag_ddl_replication_ok(CommandTag commandTag);
extern CommandTag GetCommandTagEnum(const char *commandname);
extern Size BuildQueryCompletionString(char *buff, const QueryCompletion *qc,
bool nameonly);
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index e738ac1c09..18b69e4bb3 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -23,196 +23,196 @@
* textual name, so that we can bsearch on it; see GetCommandTagEnum().
*/
-/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount */
-PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
-PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
-PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false)
-PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false)
-PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false)
-PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false)
-PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false)
-PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true)
-PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true)
-PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
-PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
-PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
-PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true)
-PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
-PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
-PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
-PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_MERGE, "MERGE", false, false, true)
-PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true)
-PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false)
-PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false)
-PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false)
-PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false)
-PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false)
-PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false)
-PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true)
-PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false)
-PG_CMDTAG(CMDTAG_SET, "SET", false, false, false)
-PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false)
-PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false)
-PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
-PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount, ddlreplok */
+PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, true, false, true)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false, true)
+PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false, false)
+PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false, false)
+PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false, false)
+PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false, true)
+PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false, false)
+PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false, false)
+PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true, false)
+PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false, false)
+PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true, false)
+PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false, false)
+PG_CMDTAG(CMDTAG_DO, "DO", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false, false)
+PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false, false)
+PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true, false)
+PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false, true)
+PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true, false)
+PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false, false)
+PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false, false)
+PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_MERGE, "MERGE", false, false, true, false)
+PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true, false)
+PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false, false)
+PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false, false)
+PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false, false)
+PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false, false)
+PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false, false)
+PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false, false)
+PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false, true)
+PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false, false)
+PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false, false)
+PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false, false)
+PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false, true)
+PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false, false)
+PG_CMDTAG(CMDTAG_SET, "SET", false, false, false, false)
+PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false, false)
+PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false, false)
+PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false, false)
+PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false, false)
+PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true, false)
+PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false, false)
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 31f84e90eb..d75334a819 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -17,10 +17,12 @@
#include "access/tupdesc.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/objectaddress.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
#include "nodes/bitmapset.h"
+#include "nodes/lockoptions.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
#include "storage/block.h"
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index c00e28361c..1c6f33095c 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -6223,9 +6223,9 @@ List of schemas
(0 rows)
\dRp "no.such.publication"
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+-------+------------+---------+---------+---------+-----------+----------
+ List of publications
+ Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+------+-------+------------+------------+---------+---------+---------+-----------+----------
(0 rows)
\dRs "no.such.subscription"
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 427f87ea07..7d86977b29 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -30,20 +30,20 @@ ERROR: conflicting or redundant options
LINE 1: ...ub_xxx WITH (publish_via_partition_root = 'true', publish_vi...
^
\dRp
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f
- testpub_default | regress_publication_user | f | f | t | f | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | f | t | f | f | f | f
+ testpub_default | regress_publication_user | f | f | f | t | f | f | f
(2 rows)
ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
\dRp
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f
- testpub_default | regress_publication_user | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | f | t | f | f | f | f
+ testpub_default | regress_publication_user | f | f | t | t | t | f | f
(2 rows)
--- adding tables
@@ -87,10 +87,10 @@ RESET client_min_messages;
-- should be able to add schema to 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable ADD TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl1"
Tables from schemas:
@@ -99,20 +99,20 @@ Tables from schemas:
-- should be able to drop schema from 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable DROP TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl1"
-- should be able to set schema to 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test"
@@ -123,10 +123,10 @@ CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test;
CREATE PUBLICATION testpub_for_tbl_schema FOR TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
RESET client_min_messages;
\dRp+ testpub_for_tbl_schema
- Publication testpub_for_tbl_schema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_for_tbl_schema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
Tables from schemas:
@@ -135,10 +135,10 @@ Tables from schemas:
-- should be able to add a table of the same schema to the schema publication
ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
Tables from schemas:
@@ -147,10 +147,10 @@ Tables from schemas:
-- should be able to drop the table
ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test"
@@ -161,10 +161,10 @@ ERROR: relation "testpub_nopk" is not part of the publication
-- should be able to set table to schema publication
ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
@@ -186,10 +186,10 @@ Publications:
"testpub_foralltables"
\dRp+ testpub_foralltables
- Publication testpub_foralltables
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | t | t | t | f | f | f
+ Publication testpub_foralltables
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | t | f | t | t | f | f | f
(1 row)
DROP TABLE testpub_tbl2;
@@ -201,19 +201,19 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3;
CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3;
RESET client_min_messages;
\dRp+ testpub3
- Publication testpub3
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub3
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl3"
"public.testpub_tbl3a"
\dRp+ testpub4
- Publication testpub4
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub4
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl3"
@@ -234,10 +234,10 @@ UPDATE testpub_parted1 SET a = 1;
-- only parent is listed as being in publication, not the partition
ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted;
\dRp+ testpub_forparted
- Publication testpub_forparted
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forparted
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"public.testpub_parted"
@@ -252,10 +252,10 @@ ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
UPDATE testpub_parted1 SET a = 1;
ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true);
\dRp+ testpub_forparted
- Publication testpub_forparted
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | t
+ Publication testpub_forparted
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -284,10 +284,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub5 FOR TABLE testpub_rf_tbl1, testpub_rf_tbl2 WHERE (c <> 'test' AND d < 5) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -300,10 +300,10 @@ Tables:
ALTER PUBLICATION testpub5 ADD TABLE testpub_rf_tbl3 WHERE (e > 1000 AND e < 2000);
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -319,10 +319,10 @@ Publications:
ALTER PUBLICATION testpub5 DROP TABLE testpub_rf_tbl2;
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl3" WHERE ((e > 1000) AND (e < 2000))
@@ -330,10 +330,10 @@ Tables:
-- remove testpub_rf_tbl1 and add testpub_rf_tbl3 again (another WHERE expression)
ALTER PUBLICATION testpub5 SET TABLE testpub_rf_tbl3 WHERE (e > 300 AND e < 500);
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl3" WHERE ((e > 300) AND (e < 500))
@@ -366,10 +366,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_syntax1 FOR TABLE testpub_rf_tbl1, ONLY testpub_rf_tbl3 WHERE (e < 999) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub_syntax1
- Publication testpub_syntax1
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub_syntax1
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl3" WHERE (e < 999)
@@ -379,10 +379,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_syntax2 FOR TABLE testpub_rf_tbl1, testpub_rf_schema1.testpub_rf_tbl5 WHERE (h < 999) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub_syntax2
- Publication testpub_syntax2
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub_syntax2
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"testpub_rf_schema1.testpub_rf_tbl5" WHERE (h < 999)
@@ -497,10 +497,10 @@ CREATE PUBLICATION testpub6 FOR TABLES IN SCHEMA testpub_rf_schema2;
ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpub_rf_schema2.testpub_rf_tbl6 WHERE (i < 99);
RESET client_min_messages;
\dRp+ testpub6
- Publication testpub6
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub6
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"testpub_rf_schema2.testpub_rf_tbl6" WHERE (i < 99)
Tables from schemas:
@@ -714,10 +714,10 @@ CREATE PUBLICATION testpub_table_ins WITH (publish = 'insert, truncate');
RESET client_min_messages;
ALTER PUBLICATION testpub_table_ins ADD TABLE testpub_tbl5 (a); -- ok
\dRp+ testpub_table_ins
- Publication testpub_table_ins
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | t | f
+ Publication testpub_table_ins
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | f | f | t | f
Tables:
"public.testpub_tbl5" (a)
@@ -891,10 +891,10 @@ CREATE TABLE testpub_tbl_both_filters (a int, b int, c int, PRIMARY KEY (a,c));
ALTER TABLE testpub_tbl_both_filters REPLICA IDENTITY USING INDEX testpub_tbl_both_filters_pkey;
ALTER PUBLICATION testpub_both_filters ADD TABLE testpub_tbl_both_filters (a,c) WHERE (c != 1);
\dRp+ testpub_both_filters
- Publication testpub_both_filters
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_both_filters
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl_both_filters" (a, c) WHERE (c <> 1)
@@ -1099,10 +1099,10 @@ ERROR: relation "testpub_tbl1" is already member of publication "testpub_fortbl
CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1;
ERROR: publication "testpub_fortbl" already exists
\dRp+ testpub_fortbl
- Publication testpub_fortbl
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortbl
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -1140,10 +1140,10 @@ Publications:
"testpub_fortbl"
\dRp+ testpub_default
- Publication testpub_default
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | f | f
+ Publication testpub_default
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | f | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -1221,10 +1221,10 @@ REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
- Publication testpub_default
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | f | f
+ Publication testpub_default
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | f | f
(1 row)
-- fail - must be owner of publication
@@ -1234,20 +1234,20 @@ ERROR: must be owner of publication testpub_default
RESET ROLE;
ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
\dRp testpub_foo
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
--------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpub_foo | regress_publication_user | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+-------------+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ testpub_foo | regress_publication_user | f | f | t | t | t | f | f
(1 row)
-- rename back to keep the rest simple
ALTER PUBLICATION testpub_foo RENAME TO testpub_default;
ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
------------------+---------------------------+------------+---------+---------+---------+-----------+----------
- testpub_default | regress_publication_user2 | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+-----------------+---------------------------+------------+------------+---------+---------+---------+-----------+----------
+ testpub_default | regress_publication_user2 | f | f | t | t | t | f | f
(1 row)
-- adding schemas and tables
@@ -1263,19 +1263,19 @@ CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub1_forschema FOR TABLES IN SCHEMA pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
CREATE PUBLICATION testpub2_forschema FOR TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1289,44 +1289,44 @@ CREATE PUBLICATION testpub6_forschema FOR TABLES IN SCHEMA "CURRENT_SCHEMA", CUR
CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
RESET client_min_messages;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"public"
\dRp+ testpub4_forschema
- Publication testpub4_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub4_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"CURRENT_SCHEMA"
\dRp+ testpub5_forschema
- Publication testpub5_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub5_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"CURRENT_SCHEMA"
"public"
\dRp+ testpub6_forschema
- Publication testpub6_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub6_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"CURRENT_SCHEMA"
"public"
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"CURRENT_SCHEMA.CURRENT_SCHEMA"
@@ -1360,10 +1360,10 @@ ERROR: schema "testpub_view" does not exist
-- dropping the schema should reflect the change in publication
DROP SCHEMA pub_test3;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1371,20 +1371,20 @@ Tables from schemas:
-- renaming the schema should reflect the change in publication
ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1_renamed"
"pub_test2"
ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1392,10 +1392,10 @@ Tables from schemas:
-- alter publication add schema
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1404,10 +1404,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1416,10 +1416,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test1;
ERROR: schema "pub_test1" is already member of publication "testpub1_forschema"
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1427,10 +1427,10 @@ Tables from schemas:
-- alter publication drop schema
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1438,10 +1438,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
ERROR: tables from schema "pub_test2" are not part of the publication
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1449,29 +1449,29 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
-- drop all schemas
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
(1 row)
-- alter publication set multiple schema
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1480,10 +1480,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1492,10 +1492,10 @@ Tables from schemas:
-- removing the duplicate schemas
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1574,18 +1574,18 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub3_forschema;
RESET client_min_messages;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
(1 row)
ALTER PUBLICATION testpub3_forschema SET TABLES IN SCHEMA pub_test1;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1595,20 +1595,20 @@ CREATE PUBLICATION testpub_forschema_fortable FOR TABLES IN SCHEMA pub_test1, TA
CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, TABLES IN SCHEMA pub_test1;
RESET client_min_messages;
\dRp+ testpub_forschema_fortable
- Publication testpub_forschema_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forschema_fortable
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"pub_test2.tbl1"
Tables from schemas:
"pub_test1"
\dRp+ testpub_fortable_forschema
- Publication testpub_fortable_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable_forschema
+ Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | t | t | t | t | f
Tables:
"pub_test2.tbl1"
Tables from schemas:
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 852ee60934..328c0167c6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -562,6 +562,7 @@ DefElemAction
DefaultACLInfo
DefineStmt
DeleteStmt
+DeparsedCommandType
DependencyGenerator
DependencyGeneratorData
DependencyType
@@ -1453,6 +1454,8 @@ LogicalDecodeBeginPrepareCB
LogicalDecodeChangeCB
LogicalDecodeCommitCB
LogicalDecodeCommitPreparedCB
+LogicalDecodeDDLMessageCB
+LogicalDecodeStreamDDLMessageCB
LogicalDecodeFilterByOriginCB
LogicalDecodeFilterPrepareCB
LogicalDecodeMessageCB
@@ -2310,6 +2313,8 @@ ReorderBufferChange
ReorderBufferChangeType
ReorderBufferCommitCB
ReorderBufferCommitPreparedCB
+ReorderBufferDDLMessageCB
+ReorderBufferStreamDDLMessageCB
ReorderBufferDiskChange
ReorderBufferIterTXNEntry
ReorderBufferIterTXNState
--
2.30.0.windows.2
[application/octet-stream] 0003-Deparser-for-INDEX-DDL-commands-2023_04_07-2.patch (14.5K, 5-0003-Deparser-for-INDEX-DDL-commands-2023_04_07-2.patch)
download | inline diff:
From 8ea302764c5033668f91e8dbea23b2a2b5688861 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <houzj.fnst@cn.fujitsu.com>
Date: Tue, 4 Apr 2023 19:12:32 +0800
Subject: [PATCH 3/6] Deparser for INDEX DDL commands
---
doc/src/sgml/logical-replication.sgml | 12 +-
src/backend/commands/ddl_deparse.c | 375 ++++++++++++++++++++++++++
2 files changed, 384 insertions(+), 3 deletions(-)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 37214baa3d..75b395222b 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1500,6 +1500,12 @@ test_sub=# SELECT * FROM t1 ORDER BY id;
</itemizedlist>
</para>
</listitem>
+ <listitem>
+ <para>
+ index: this option enables replication of Index DDL commands,
+ which include CREATE/ALTER/DROP INDEX.
+ </para>
+ </listitem>
</itemizedlist>
<sect2 id="ddl-replication-option-examples">
@@ -1604,7 +1610,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>ALTER INDEX</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1884,7 +1890,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>CREATE INDEX</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2139,7 +2145,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>DROP INDEX</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
diff --git a/src/backend/commands/ddl_deparse.c b/src/backend/commands/ddl_deparse.c
index 6efe84498f..83f2eeeb0e 100644
--- a/src/backend/commands/ddl_deparse.c
+++ b/src/backend/commands/ddl_deparse.c
@@ -1037,6 +1037,244 @@ obtainConstraints(List *elements, Oid relationId, Oid domainId,
return elements;
}
+/*
+ * Return an index definition, split into several pieces.
+ *
+ * A large amount of code is duplicated from pg_get_indexdef_worker, but
+ * control flow is different enough that it doesn't seem worth keeping them
+ * together.
+ */
+static void
+pg_get_indexdef_detailed(Oid indexrelid,
+ char **index_am,
+ char **definition,
+ char **reloptions,
+ char **tablespace,
+ char **whereClause)
+{
+ HeapTuple ht_idx;
+ HeapTuple ht_idxrel;
+ HeapTuple ht_am;
+ Form_pg_index idxrec;
+ Form_pg_class idxrelrec;
+ Form_pg_am amrec;
+ IndexAmRoutine *amroutine;
+ List *indexprs;
+ ListCell *indexpr_item;
+ List *context;
+ Oid indrelid;
+ int keyno;
+ Datum indcollDatum;
+ Datum indclassDatum;
+ Datum indoptionDatum;
+ bool isnull;
+ oidvector *indcollation;
+ oidvector *indclass;
+ int2vector *indoption;
+ StringInfoData definitionBuf;
+
+ *tablespace = NULL;
+ *whereClause = NULL;
+
+ /* Fetch the pg_index tuple by the Oid of the index */
+ ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
+ if (!HeapTupleIsValid(ht_idx))
+ elog(ERROR, "cache lookup failed for index with OID %u", indexrelid);
+ idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+
+ indrelid = idxrec->indrelid;
+ Assert(indexrelid == idxrec->indexrelid);
+
+ /* Must get indcollation, indclass, and indoption the hard way */
+ indcollDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indcollation, &isnull);
+ Assert(!isnull);
+ indcollation = (oidvector *) DatumGetPointer(indcollDatum);
+
+ indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indclass, &isnull);
+ Assert(!isnull);
+ indclass = (oidvector *) DatumGetPointer(indclassDatum);
+
+ indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indoption, &isnull);
+ Assert(!isnull);
+ indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+
+ /* Fetch the pg_class tuple of the index relation */
+ ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid));
+ if (!HeapTupleIsValid(ht_idxrel))
+ elog(ERROR, "cache lookup failed for relation with OID %u", indexrelid);
+ idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+
+ /* Fetch the pg_am tuple of the index' access method */
+ ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam));
+ if (!HeapTupleIsValid(ht_am))
+ elog(ERROR, "cache lookup failed for access method with OID %u",
+ idxrelrec->relam);
+ amrec = (Form_pg_am) GETSTRUCT(ht_am);
+
+ /*
+ * Get the index expressions, if any. (NOTE: we do not use the relcache
+ * versions of the expressions and predicate, because we want to display
+ * non-const-folded expressions.)
+ */
+ if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL))
+ {
+ Datum exprsDatum;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ indexprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ }
+ else
+ indexprs = NIL;
+
+ indexpr_item = list_head(indexprs);
+
+ context = deparse_context_for(get_rel_name(indrelid), indrelid);
+
+ initStringInfo(&definitionBuf);
+
+ /* Output index AM */
+ *index_am = pstrdup(quote_identifier(NameStr(amrec->amname)));
+
+ /* Fetch the index AM's API struct */
+ amroutine = GetIndexAmRoutine(amrec->amhandler);
+
+ /*
+ * Output index definition. Note the outer parens must be supplied by
+ * caller.
+ */
+ appendStringInfoString(&definitionBuf, "(");
+ for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ {
+ AttrNumber attnum = idxrec->indkey.values[keyno];
+ int16 opt = indoption->values[keyno];
+ Oid keycoltype;
+ Oid keycolcollation;
+
+ /* Print INCLUDE to divide key and non-key attrs. */
+ if (keyno == idxrec->indnkeyatts)
+ {
+ appendStringInfoString(&definitionBuf, ") INCLUDE (");
+ }
+ else
+ appendStringInfoString(&definitionBuf, keyno == 0 ? "" : ", ");
+
+ if (attnum != 0)
+ {
+ /* Simple index column */
+ char *attname;
+ int32 keycoltypmod;
+
+ attname = get_attname(indrelid, attnum, false);
+ appendStringInfoString(&definitionBuf, quote_identifier(attname));
+ get_atttypetypmodcoll(indrelid, attnum,
+ &keycoltype, &keycoltypmod,
+ &keycolcollation);
+ }
+ else
+ {
+ /* Expressional index */
+ Node *indexkey;
+ char *str;
+
+ if (indexpr_item == NULL)
+ elog(ERROR, "too few entries in indexprs list");
+ indexkey = (Node *) lfirst(indexpr_item);
+ indexpr_item = lnext(indexprs, indexpr_item);
+
+ /* Deparse */
+ str = deparse_expression(indexkey, context, false, false);
+
+ /* Need parens if it's not a bare function call */
+ if (indexkey && IsA(indexkey, FuncExpr) &&
+ ((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+ appendStringInfoString(&definitionBuf, str);
+ else
+ appendStringInfo(&definitionBuf, "(%s)", str);
+
+ keycoltype = exprType(indexkey);
+ keycolcollation = exprCollation(indexkey);
+ }
+
+ /* Print additional decoration for (selected) key columns, even if default */
+ if (keyno < idxrec->indnkeyatts)
+ {
+ Oid indcoll = indcollation->values[keyno];
+ if (OidIsValid(indcoll))
+ appendStringInfo(&definitionBuf, " COLLATE %s",
+ generate_collation_name((indcoll)));
+
+ /* Add the operator class name, even if default */
+ get_opclass_name(indclass->values[keyno], InvalidOid, &definitionBuf);
+
+ /* Add options if relevant */
+ if (amroutine->amcanorder)
+ {
+ /* If it supports sort ordering, report DESC and NULLS opts */
+ if (opt & INDOPTION_DESC)
+ {
+ appendStringInfoString(&definitionBuf, " DESC");
+ /* NULLS FIRST is the default in this case */
+ if (!(opt & INDOPTION_NULLS_FIRST))
+ appendStringInfoString(&definitionBuf, " NULLS LAST");
+ }
+ else
+ {
+ if (opt & INDOPTION_NULLS_FIRST)
+ appendStringInfoString(&definitionBuf, " NULLS FIRST");
+ }
+ }
+
+ /* XXX excludeOps thingy was here; do we need anything? */
+ }
+ }
+ appendStringInfoString(&definitionBuf, ")");
+ *definition = definitionBuf.data;
+
+ /* Output reloptions */
+ *reloptions = flatten_reloptions(indexrelid);
+
+ /* Output tablespace */
+ {
+ Oid tblspc;
+
+ tblspc = get_rel_tablespace(indexrelid);
+ if (OidIsValid(tblspc))
+ *tablespace = pstrdup(quote_identifier(get_tablespace_name(tblspc)));
+ }
+
+ /* Report index predicate, if any */
+ if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL))
+ {
+ Node *node;
+ Datum predDatum;
+ char *predString;
+
+ /* Convert text string to node tree */
+ predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indpred, &isnull);
+ Assert(!isnull);
+ predString = TextDatumGetCString(predDatum);
+ node = (Node *) stringToNode(predString);
+ pfree(predString);
+
+ /* Deparse */
+ *whereClause = deparse_expression(node, context, false, false);
+ }
+
+ /* Clean up */
+ ReleaseSysCache(ht_idx);
+ ReleaseSysCache(ht_idxrel);
+ ReleaseSysCache(ht_am);
+}
+
/*
* Obtain the deparsed default value for the given column of the given table.
*
@@ -1950,6 +2188,102 @@ deparse_TableElements(Relation relation, List *tableElements, List *dpcontext,
return elements;
}
+/*
+ * Deparse an IndexStmt.
+ *
+ * Given an index OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ *
+ * Verbose syntax
+ * CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I ON
+ * %{only}s %{table}D USING %{index_am}s %{definition}s %{nulls_not_distinct}s
+ * %{with}s %{tablespace}s %{where_clause}s
+ */
+static ObjTree *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+ IndexStmt *node = (IndexStmt *) parsetree;
+ ObjTree *ret;
+ ObjTree *tmp_obj;
+ Relation idxrel;
+ Relation heaprel;
+ char *index_am;
+ char *definition;
+ char *reloptions;
+ char *tablespace;
+ char *whereClause;
+
+ if (node->primary || node->isconstraint)
+ {
+ /*
+ * Indexes for PRIMARY KEY and other constraints are output
+ * separately; return empty here.
+ */
+ return NULL;
+ }
+
+ idxrel = relation_open(objectId, AccessShareLock);
+ heaprel = relation_open(idxrel->rd_index->indrelid, AccessShareLock);
+
+ pg_get_indexdef_detailed(objectId,
+ &index_am, &definition, &reloptions,
+ &tablespace, &whereClause);
+
+ ret = new_objtree_VA("CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I ON %{only}s %{table}D USING %{index_am}s %{definition}s", 8,
+ "unique", ObjTypeString,
+ node->unique ? "UNIQUE" : "",
+ "concurrently", ObjTypeString,
+ node->concurrent ? "CONCURRENTLY" : "",
+ "if_not_exists", ObjTypeString,
+ node->if_not_exists ? "IF NOT EXISTS" : "",
+ "only", ObjTypeString,
+ node->relation->inh ? "" : "ONLY",
+ "name", ObjTypeString,
+ RelationGetRelationName(idxrel),
+ "table", ObjTypeObject,
+ new_objtree_for_qualname(heaprel->rd_rel->relnamespace,
+ RelationGetRelationName(heaprel)),
+ "index_am", ObjTypeString, index_am,
+ "definition", ObjTypeString, definition);
+
+ /* nulls_not_distinct */
+ if (node->nulls_not_distinct)
+ append_format_string(ret, "NULLS NOT DISTINCT");
+ else
+ append_format_string(ret, "NULLS DISTINCT");
+
+ /* reloptions */
+ tmp_obj = new_objtree("WITH");
+ if (reloptions)
+ append_string_object(tmp_obj, "(%{opts}s)", "opts", reloptions);
+ else
+ append_not_present(tmp_obj);
+ append_object_object(ret, "%{with}s", tmp_obj);
+
+ /* tablespace */
+ tmp_obj = new_objtree("TABLESPACE");
+ if (tablespace)
+ append_string_object(tmp_obj, "%{tablespace}s", "tablespace", tablespace);
+ else
+ append_not_present(tmp_obj);
+ append_object_object(ret, "%{tablespace}s", tmp_obj);
+
+ /* WHERE clause */
+ tmp_obj = new_objtree("WHERE");
+ if (whereClause)
+ append_string_object(tmp_obj, "%{where}s", "where", whereClause);
+ else
+ append_not_present(tmp_obj);
+ append_object_object(ret, "%{where_clause}s", tmp_obj);
+
+ table_close(idxrel, AccessShareLock);
+ table_close(heaprel, AccessShareLock);
+
+ return ret;
+}
+
/*
* Deparse a CreateStmt (CREATE TABLE).
*
@@ -3167,6 +3501,7 @@ deparse_RenameStmt(ObjectAddress address, Node *parsetree)
*/
switch (node->renameType)
{
+ case OBJECT_INDEX:
case OBJECT_TABLE:
relation = relation_open(address.objectId, AccessShareLock);
schemaId = RelationGetNamespace(relation);
@@ -3249,6 +3584,40 @@ deparse_RenameStmt(ObjectAddress address, Node *parsetree)
return ret;
}
+/*
+ * Deparse a AlterObjectDependsStmt (ALTER ... DEPENDS ON ...).
+ *
+ * Verbose syntax
+ * ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I
+ */
+static ObjTree *
+deparse_AlterDependStmt(Oid objectId, Node *parsetree)
+{
+ AlterObjectDependsStmt *node = (AlterObjectDependsStmt *) parsetree;
+ ObjTree *ret = NULL;
+
+ if (node->objectType == OBJECT_INDEX)
+ {
+ ObjTree *qualified;
+ Relation relation = relation_open(objectId, AccessShareLock);
+
+ qualified = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+ node->relation->relname);
+ relation_close(relation, AccessShareLock);
+
+ ret = new_objtree_VA("ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I", 3,
+ "identity", ObjTypeObject, qualified,
+ "no", ObjTypeString,
+ node->remove ? "NO" : "",
+ "newname", ObjTypeString, strVal(node->extname));
+ }
+ else
+ elog(LOG, "unrecognized node type in deparse command: %d",
+ (int) nodeTag(parsetree));
+
+ return ret;
+}
+
/*
* Handle deparsing of simple commands.
*
@@ -3271,6 +3640,9 @@ deparse_simple_command(CollectedCommand *cmd)
/* This switch needs to handle everything that ProcessUtilitySlow does */
switch (nodeTag(parsetree))
{
+ case T_AlterObjectDependsStmt:
+ return deparse_AlterDependStmt(objectId, parsetree);
+
case T_AlterObjectSchemaStmt:
return deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
parsetree,
@@ -3282,6 +3654,9 @@ deparse_simple_command(CollectedCommand *cmd)
case T_CreateStmt:
return deparse_CreateStmt(objectId, parsetree);
+ case T_IndexStmt:
+ return deparse_IndexStmt(objectId, parsetree);
+
case T_RenameStmt:
return deparse_RenameStmt(cmd->d.simple.address, parsetree);
--
2.30.0.windows.2
[application/octet-stream] 0004-DDL-replication-for-index-DDL-commands-2023_04_07-2.patch (77.3K, 6-0004-DDL-replication-for-index-DDL-commands-2023_04_07-2.patch)
download | inline diff:
From 20c75ed6a92ad7aceb7012ac94aeb0662d1a624b Mon Sep 17 00:00:00 2001
From: Hou Zhijie <houzj.fnst@cn.fujitsu.com>
Date: Thu, 6 Apr 2023 20:12:20 +0800
Subject: [PATCH 4/6] DDL replication for index DDL commands
---
src/backend/catalog/pg_publication.c | 1 +
src/backend/commands/publicationcmds.c | 29 +-
src/backend/replication/logical/ddltrigger.c | 13 +-
src/backend/replication/logical/logical.c | 32 +-
src/backend/replication/pgoutput/pgoutput.c | 79 +++-
src/backend/utils/cache/relcache.c | 1 +
src/bin/pg_dump/pg_dump.c | 36 +-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 21 +-
src/include/catalog/pg_publication.h | 4 +
src/include/replication/ddlmessage.h | 3 +-
src/test/regress/expected/psql.out | 6 +-
src/test/regress/expected/publication.out | 420 +++++++++----------
13 files changed, 397 insertions(+), 249 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index d209602e28..1a3105e025 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1029,6 +1029,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
pub->pubactions.pubddl_table = pubform->pubddl_table;
+ pub->pubactions.pubddl_index = pubform->pubddl_index;
pub->pubviaroot = pubform->pubviaroot;
ReleaseSysCache(tup);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 64423ff35f..e3aad1c883 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -103,6 +103,7 @@ parse_publication_options(ParseState *pstate,
pubactions->pubdelete = true;
pubactions->pubtruncate = true;
pubactions->pubddl_table = false;
+ pubactions->pubddl_index = false;
*publish_via_partition_root = false;
/* Parse options */
@@ -178,6 +179,7 @@ parse_publication_options(ParseState *pstate,
* should be published.
*/
pubactions->pubddl_table = false;
+ pubactions->pubddl_index = false;
*ddl_type_given = true;
ddl_level = defGetString(defel);
@@ -194,6 +196,8 @@ parse_publication_options(ParseState *pstate,
if (strcmp(publish_opt, "table") == 0)
pubactions->pubddl_table = true;
+ else if (strcmp(publish_opt, "index") == 0)
+ pubactions->pubddl_index = true;
else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
@@ -856,6 +860,15 @@ CreateDDLReplicaEventTriggers(PublicationActions pubactions, Oid puboid)
end_commands = lappend_int(end_commands, CMDTAG_DROP_TABLE);
}
+ if (pubactions.pubddl_index)
+ {
+ start_commands = lappend_int(start_commands, CMDTAG_DROP_INDEX);
+
+ end_commands = lappend_int(end_commands, CMDTAG_CREATE_INDEX);
+ end_commands = lappend_int(end_commands, CMDTAG_ALTER_INDEX);
+ end_commands = lappend_int(end_commands, CMDTAG_DROP_INDEX);
+ }
+
/* Create the ddl_command_end event trigger */
if (end_commands != NIL)
CreateDDLReplicaEventTrigger(PUB_TRIG_EVENT1, end_commands, puboid);
@@ -992,6 +1005,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
BoolGetDatum(pubactions.pubtruncate);
values[Anum_pg_publication_pubddl_table - 1] =
BoolGetDatum(pubactions.pubddl_table);
+ values[Anum_pg_publication_pubddl_index - 1] =
+ BoolGetDatum(pubactions.pubddl_index);
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
@@ -1029,7 +1044,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
- if (pubactions.pubddl_table)
+ if (pubactions.pubddl_table || pubactions.pubddl_index)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
@@ -1188,7 +1203,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
}
- if (ddl_type_given && pubactions.pubddl_table)
+ if (ddl_type_given && (pubactions.pubddl_table || pubactions.pubddl_index))
{
if (root_relids == NIL)
root_relids = GetPublicationRelations(pubform->oid,
@@ -1223,7 +1238,8 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
if (ddl_type_given)
{
/* Recreate the event triggers if the ddl option is changed. */
- if (pubform->pubddl_table != pubactions.pubddl_table)
+ if (pubform->pubddl_table != pubactions.pubddl_table ||
+ pubform->pubddl_index != pubactions.pubddl_index)
{
DropDDLReplicaEventTriggers(pubform->oid);
CreateDDLReplicaEventTriggers(pubactions, pubform->oid);
@@ -1231,6 +1247,9 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
values[Anum_pg_publication_pubddl_table - 1] = BoolGetDatum(pubactions.pubddl_table);
replaces[Anum_pg_publication_pubddl_table - 1] = true;
+
+ values[Anum_pg_publication_pubddl_index - 1] = BoolGetDatum(pubactions.pubddl_index);
+ replaces[Anum_pg_publication_pubddl_index - 1] = true;
}
if (publish_via_partition_root_given)
@@ -1338,7 +1357,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (stmt->action == AP_AddObjects)
{
- if (pubform->pubddl_table)
+ if (pubform->pubddl_table || pubform->pubddl_index)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
@@ -1362,7 +1381,7 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
List *delrels = NIL;
ListCell *oldlc;
- if (pubform->pubddl_table)
+ if (pubform->pubddl_table || pubform->pubddl_index)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
diff --git a/src/backend/replication/logical/ddltrigger.c b/src/backend/replication/logical/ddltrigger.c
index 1282340c2b..fb35cc2c7f 100644
--- a/src/backend/replication/logical/ddltrigger.c
+++ b/src/backend/replication/logical/ddltrigger.c
@@ -81,8 +81,17 @@ publication_deparse_ddl_command_start(PG_FUNCTION_ARGS)
* new table.
*/
if (relpersist == RELPERSISTENCE_PERMANENT)
- LogLogicalDDLMessage("deparse", address.objectId, DCT_TableDropStart,
+ {
+ DeparsedCommandType cmdtype;
+
+ if (stmt->removeType == OBJECT_TABLE)
+ cmdtype = DCT_TableDropStart;
+ else
+ cmdtype = DCT_ObjectDropStart;
+
+ LogLogicalDDLMessage("deparse", address.objectId, cmdtype,
command, strlen(command) + 1);
+ }
if (relation)
table_close(relation, NoLock);
@@ -225,6 +234,8 @@ publication_deparse_ddl_command_end(PG_FUNCTION_ARGS)
if (strcmp(obj->objecttype, "table") == 0)
cmdtype = DCT_TableDropEnd;
+ else if (strcmp(obj->objecttype, "index") == 0)
+ cmdtype = DCT_ObjectDropEnd;
else
continue;
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index f56a716567..533e27fb07 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -75,9 +75,9 @@ static void message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
XLogRecPtr message_lsn, bool transactional,
const char *prefix, Size message_size, const char *message);
static void ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
- XLogRecPtr message_lsn, const char *prefix,
- Oid relid, DeparsedCommandType cmdtype,
- Size message_size, const char *message);
+ XLogRecPtr message_lsn, const char *prefix,
+ Oid relid, DeparsedCommandType cmdtype,
+ Size message_size, const char *message);
/* streaming callbacks */
static void stream_start_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
@@ -96,10 +96,10 @@ static void stream_message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *tx
XLogRecPtr message_lsn, bool transactional,
const char *prefix, Size message_size, const char *message);
static void stream_ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
- XLogRecPtr message_lsn,
- const char *prefix,
- Oid relid, DeparsedCommandType cmdtype,
- Size message_size, const char *message);
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid, DeparsedCommandType cmdtype,
+ Size message_size, const char *message);
static void stream_truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
int nrelations, Relation relations[], ReorderBufferChange *change);
@@ -1248,10 +1248,10 @@ message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
static void
ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
- XLogRecPtr message_lsn,
- const char *prefix, Oid relid, DeparsedCommandType cmdtype,
- Size message_size,
- const char *message)
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message)
{
LogicalDecodingContext *ctx = cache->private_data;
LogicalErrorCallbackState state;
@@ -1278,7 +1278,7 @@ ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
/* do the actual work: call callback */
ctx->callbacks.ddl_cb(ctx, txn, message_lsn, prefix, relid, cmdtype,
- message_size, message);
+ message_size, message);
/* Pop the error context stack */
error_context_stack = errcallback.previous;
@@ -1601,10 +1601,10 @@ stream_message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
static void
stream_ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
- XLogRecPtr message_lsn,
- const char *prefix, Oid relid, DeparsedCommandType cmdtype,
- Size message_size,
- const char *message)
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message)
{
LogicalDecodingContext *ctx = cache->private_data;
LogicalErrorCallbackState state;
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 7ea9ff51ec..9a54cbd152 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -219,10 +219,20 @@ typedef struct PGOutputTxnData
/* Map used to remember which relation schemas we sent. */
static HTAB *RelationSyncCache = NULL;
+/* Struct to cache the published DDL. */
+typedef struct DDLSyncCache
+{
+ bool valid;
+ bool pubindex;
+} DDLSyncCache;
+
+static DDLSyncCache *ddlcache = NULL;
+
static void init_rel_sync_cache(MemoryContext cachectx);
static void cleanup_rel_sync_cache(TransactionId xid, bool is_commit);
static RelationSyncEntry *get_rel_sync_entry(PGOutputData *data,
Relation relation);
+static void build_ddl_sync_cache(PGOutputData *data);
static void rel_sync_cache_relation_cb(Datum arg, Oid relid);
static void rel_sync_cache_publication_cb(Datum arg, int cacheid,
uint32 hashvalue);
@@ -1760,6 +1770,27 @@ is_object_published(LogicalDecodingContext *ctx, Oid objid)
relentry->publish_as_relid != objid)
return false;
+ break;
+ case RELKIND_INDEX:
+ build_ddl_sync_cache(data);
+
+ if (!ddlcache->pubindex)
+ return false;
+
+ /* Get the table OID that the index is for. */
+ relation = RelationIdGetRelation(objid);
+ objid = relation->rd_index->indrelid;
+ RelationClose(relation);
+
+ /* Filter the index DDLs if the index's table was not published. */
+ relation = RelationIdGetRelation(objid);
+ relentry = get_rel_sync_entry(data, relation);
+ RelationClose(relation);
+
+ if (!relentry->pubactions.pubddl_table ||
+ relentry->publish_as_relid != objid)
+ return false;
+
break;
default:
/* unsupported objects */
@@ -1785,12 +1816,14 @@ pgoutput_ddl(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
* we cannot get the required information from the catalog, so we skip the
* check for them.
*/
- if (cmdtype != DCT_TableDropEnd && !is_object_published(ctx, relid))
+ if (cmdtype != DCT_TableDropEnd && cmdtype != DCT_ObjectDropEnd &&
+ !is_object_published(ctx, relid))
return;
switch (cmdtype)
{
case DCT_TableDropStart:
+ case DCT_ObjectDropStart:
{
MemoryContext old;
@@ -1814,6 +1847,7 @@ pgoutput_ddl(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
return;
case DCT_TableDropEnd:
+ case DCT_ObjectDropEnd:
if (!list_member_oid(txndata->deleted_relids, relid))
return;
@@ -1936,6 +1970,9 @@ publication_invalidation_cb(Datum arg, int cacheid, uint32 hashvalue)
* is checked it will be updated with the new publication settings.
*/
rel_sync_cache_publication_cb(arg, cacheid, hashvalue);
+
+ if (ddlcache != NULL)
+ ddlcache->valid = false;
}
/*
@@ -2203,7 +2240,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->streamed_txns = NIL;
entry->pubactions.pubinsert = entry->pubactions.pubupdate =
entry->pubactions.pubdelete = entry->pubactions.pubtruncate =
- entry->pubactions.pubddl_table = false;
+ entry->pubactions.pubddl_table = entry->pubactions.pubddl_index = false;
entry->new_slot = NULL;
entry->old_slot = NULL;
memset(entry->exprstate, 0, sizeof(entry->exprstate));
@@ -2250,6 +2287,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->pubactions.pubdelete = false;
entry->pubactions.pubtruncate = false;
entry->pubactions.pubddl_table = false;
+ entry->pubactions.pubddl_index = false;
/*
* Tuple slots cleanups. (Will be rebuilt later if needed).
@@ -2364,6 +2402,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->pubactions.pubdelete |= pub->pubactions.pubdelete;
entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate;
entry->pubactions.pubddl_table |= pub->pubactions.pubddl_table;
+ entry->pubactions.pubddl_index |= pub->pubactions.pubddl_index;
/*
* We want to publish the changes as the top-most ancestor
@@ -2428,6 +2467,42 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
return entry;
}
+/*
+ * This looks up all publications and build the cache about which DDLs to
+ * publish.
+ */
+static void
+build_ddl_sync_cache(PGOutputData *data)
+{
+ ListCell *lc;
+ MemoryContext oldctx;
+
+ if (ddlcache == NULL)
+ {
+ oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+ ddlcache = (DDLSyncCache *) palloc0(sizeof(DDLSyncCache));
+ MemoryContextSwitchTo(oldctx);
+ }
+
+ if (ddlcache->valid)
+ return;
+
+ ddlcache->pubindex = false;
+
+ reload_publications(data);
+
+ foreach(lc, data->publications)
+ {
+ Publication *pub = lfirst(lc);
+
+ ddlcache->pubindex |= pub->pubactions.pubddl_index;
+ }
+
+ ddlcache->valid = true;
+
+ return;
+}
+
/*
* Cleanup list of streamed transactions and update the schema_sent flag.
*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9198da2eb1..12259c9dd8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5721,6 +5721,7 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
pubdesc->pubactions.pubdelete |= pubform->pubdelete;
pubdesc->pubactions.pubtruncate |= pubform->pubtruncate;
pubdesc->pubactions.pubddl_table |= pubform->pubddl_table;
+ pubdesc->pubactions.pubddl_index |= pubform->pubddl_index;
/*
* Check if all columns referenced in the filter expression are part
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3c3a34267f..c34d33b9a1 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4066,6 +4066,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubdelete;
int i_pubtruncate;
int i_pubddl_table;
+ int i_pubddl_index;
int i_pubviaroot;
int i,
ntups;
@@ -4085,25 +4086,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubddl_table, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubddl_table, p.pubddl_index, p.pubviaroot "
"FROM pg_publication p");
else if (fout->remoteVersion >= 130000)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, p.pubviaroot "
"FROM pg_publication p");
else if (fout->remoteVersion >= 110000)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false AS pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false AS pubviaroot "
"FROM pg_publication p");
else
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false as p.pubddl_table, false AS pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false AS pubviaroot "
"FROM pg_publication p");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4120,6 +4121,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubddl_table = PQfnumber(res, "pubddl_table");
+ i_pubddl_index = PQfnumber(res, "pubddl_index");
i_pubviaroot = PQfnumber(res, "pubviaroot");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4145,6 +4147,8 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
pubinfo[i].pubddl_table =
(strcmp(PQgetvalue(res, i, i_pubddl_table), "t") == 0);
+ pubinfo[i].pubddl_index =
+ (strcmp(PQgetvalue(res, i, i_pubddl_index), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
@@ -4226,8 +4230,28 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
appendPQExpBufferStr(query, "'");
- if (pubinfo->pubddl_table)
- appendPQExpBufferStr(query, ", ddl = 'table'");
+ if (pubinfo->pubddl_table || pubinfo->pubddl_index)
+ {
+ first = true;
+ appendPQExpBufferStr(query, ", ddl = '");
+
+ if (pubinfo->pubddl_table)
+ {
+ appendPQExpBufferStr(query, "table");
+ first = false;
+ }
+
+ if (pubinfo->pubddl_index)
+ {
+ if (!first)
+ appendPQExpBufferStr(query, ", ");
+
+ appendPQExpBufferStr(query, "index");
+ first = false;
+ }
+
+ appendPQExpBufferStr(query, "'");
+ }
if (pubinfo->pubviaroot)
appendPQExpBufferStr(query, ", publish_via_partition_root = true");
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index bd5f8fb669..9ae6a38e00 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -621,6 +621,7 @@ typedef struct _PublicationInfo
bool pubdelete;
bool pubtruncate;
bool pubddl_table;
+ bool pubddl_index;
bool pubviaroot;
} PublicationInfo;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e977e6f464..7068dc0388 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6183,7 +6183,7 @@ listPublications(const char *pattern)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
- static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false};
+ static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false, false};
if (pset.sversion < 100000)
{
@@ -6205,9 +6205,13 @@ listPublications(const char *pattern)
gettext_noop("Owner"),
gettext_noop("All tables"));
if (pset.sversion >= 160000)
+ {
appendPQExpBuffer(&buf,
- ",\n pubddl_table AS \"%s\"",
- gettext_noop("Table DDLs"));
+ ",\n pubddl_table AS \"%s\",\n"
+ " pubddl_index AS \"%s\"\n",
+ gettext_noop("Table DDLs"),
+ gettext_noop("Index DDLs"));
+ }
appendPQExpBuffer(&buf,
",\n pubinsert AS \"%s\",\n"
" pubupdate AS \"%s\",\n"
@@ -6339,7 +6343,8 @@ describePublications(const char *pattern)
" puballtables");
if (has_pubddl)
appendPQExpBufferStr(&buf,
- ", pubddl_table");
+ ", pubddl_table, pubddl_index");
+
appendPQExpBufferStr(&buf,
", pubinsert, pubupdate, pubdelete");
if (has_pubtruncate)
@@ -6400,7 +6405,7 @@ describePublications(const char *pattern)
if (has_pubviaroot)
ncols++;
if (has_pubddl)
- ncols++;
+ ncols += 2;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6409,7 +6414,10 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Owner"), true, align);
printTableAddHeader(&cont, gettext_noop("All tables"), true, align);
if (has_pubddl)
+ {
printTableAddHeader(&cont, gettext_noop("Table DDLs"), true, align);
+ printTableAddHeader(&cont, gettext_noop("Index DDLs"), true, align);
+ }
printTableAddHeader(&cont, gettext_noop("Inserts"), true, align);
printTableAddHeader(&cont, gettext_noop("Updates"), true, align);
printTableAddHeader(&cont, gettext_noop("Deletes"), true, align);
@@ -6428,7 +6436,10 @@ describePublications(const char *pattern)
if (has_pubviaroot)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
if (has_pubddl)
+ {
printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
+ printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
+ }
if (!puballtables)
{
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 74b28d7350..467731c61a 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -66,6 +66,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if table ddls are published */
bool pubddl_table;
+
+ /* true if index ddls are published */
+ bool pubddl_index;
} FormData_pg_publication;
/* ----------------
@@ -85,6 +88,7 @@ typedef struct PublicationActions
bool pubdelete;
bool pubtruncate;
bool pubddl_table;
+ bool pubddl_index;
} PublicationActions;
typedef struct PublicationDesc
diff --git a/src/include/replication/ddlmessage.h b/src/include/replication/ddlmessage.h
index 77df6ea11a..1a4aca8dd5 100644
--- a/src/include/replication/ddlmessage.h
+++ b/src/include/replication/ddlmessage.h
@@ -26,7 +26,8 @@ typedef enum DeparsedCommandType
DCT_TableDropEnd,
DCT_TableAlter,
DCT_ObjectCreate,
- DCT_ObjectDrop
+ DCT_ObjectDropStart,
+ DCT_ObjectDropEnd
} DeparsedCommandType;
/*
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1c6f33095c..3e6d2791c8 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -6223,9 +6223,9 @@ List of schemas
(0 rows)
\dRp "no.such.publication"
- List of publications
- Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
-------+-------+------------+------------+---------+---------+---------+-----------+----------
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+------+-------+------------+------------+------------+---------+---------+---------+-----------+----------
(0 rows)
\dRs "no.such.subscription"
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 7d86977b29..ebcff41c0c 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -30,20 +30,20 @@ ERROR: conflicting or redundant options
LINE 1: ...ub_xxx WITH (publish_via_partition_root = 'true', publish_vi...
^
\dRp
- List of publications
- Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------+--------------------------+------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f | f | t | f | f | f | f
- testpub_default | regress_publication_user | f | f | f | t | f | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | f | f | t | f | f | f | f
+ testpub_default | regress_publication_user | f | f | f | f | t | f | f | f
(2 rows)
ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
\dRp
- List of publications
- Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------+--------------------------+------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f | f | t | f | f | f | f
- testpub_default | regress_publication_user | f | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | f | f | t | f | f | f | f
+ testpub_default | regress_publication_user | f | f | f | t | t | t | f | f
(2 rows)
--- adding tables
@@ -87,10 +87,10 @@ RESET client_min_messages;
-- should be able to add schema to 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable ADD TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl1"
Tables from schemas:
@@ -99,20 +99,20 @@ Tables from schemas:
-- should be able to drop schema from 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable DROP TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl1"
-- should be able to set schema to 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test"
@@ -123,10 +123,10 @@ CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test;
CREATE PUBLICATION testpub_for_tbl_schema FOR TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
RESET client_min_messages;
\dRp+ testpub_for_tbl_schema
- Publication testpub_for_tbl_schema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_for_tbl_schema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
Tables from schemas:
@@ -135,10 +135,10 @@ Tables from schemas:
-- should be able to add a table of the same schema to the schema publication
ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
Tables from schemas:
@@ -147,10 +147,10 @@ Tables from schemas:
-- should be able to drop the table
ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test"
@@ -161,10 +161,10 @@ ERROR: relation "testpub_nopk" is not part of the publication
-- should be able to set table to schema publication
ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
@@ -186,10 +186,10 @@ Publications:
"testpub_foralltables"
\dRp+ testpub_foralltables
- Publication testpub_foralltables
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | t | f | t | t | f | f | f
+ Publication testpub_foralltables
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | t | f | f | t | t | f | f | f
(1 row)
DROP TABLE testpub_tbl2;
@@ -201,19 +201,19 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3;
CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3;
RESET client_min_messages;
\dRp+ testpub3
- Publication testpub3
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub3
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl3"
"public.testpub_tbl3a"
\dRp+ testpub4
- Publication testpub4
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub4
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl3"
@@ -234,10 +234,10 @@ UPDATE testpub_parted1 SET a = 1;
-- only parent is listed as being in publication, not the partition
ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted;
\dRp+ testpub_forparted
- Publication testpub_forparted
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_forparted
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"public.testpub_parted"
@@ -252,10 +252,10 @@ ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
UPDATE testpub_parted1 SET a = 1;
ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true);
\dRp+ testpub_forparted
- Publication testpub_forparted
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | t
+ Publication testpub_forparted
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -284,10 +284,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub5 FOR TABLE testpub_rf_tbl1, testpub_rf_tbl2 WHERE (c <> 'test' AND d < 5) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -300,10 +300,10 @@ Tables:
ALTER PUBLICATION testpub5 ADD TABLE testpub_rf_tbl3 WHERE (e > 1000 AND e < 2000);
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -319,10 +319,10 @@ Publications:
ALTER PUBLICATION testpub5 DROP TABLE testpub_rf_tbl2;
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl3" WHERE ((e > 1000) AND (e < 2000))
@@ -330,10 +330,10 @@ Tables:
-- remove testpub_rf_tbl1 and add testpub_rf_tbl3 again (another WHERE expression)
ALTER PUBLICATION testpub5 SET TABLE testpub_rf_tbl3 WHERE (e > 300 AND e < 500);
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl3" WHERE ((e > 300) AND (e < 500))
@@ -366,10 +366,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_syntax1 FOR TABLE testpub_rf_tbl1, ONLY testpub_rf_tbl3 WHERE (e < 999) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub_syntax1
- Publication testpub_syntax1
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | f | f | f | f
+ Publication testpub_syntax1
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl3" WHERE (e < 999)
@@ -379,10 +379,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_syntax2 FOR TABLE testpub_rf_tbl1, testpub_rf_schema1.testpub_rf_tbl5 WHERE (h < 999) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub_syntax2
- Publication testpub_syntax2
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | f | f | f | f
+ Publication testpub_syntax2
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"testpub_rf_schema1.testpub_rf_tbl5" WHERE (h < 999)
@@ -497,10 +497,10 @@ CREATE PUBLICATION testpub6 FOR TABLES IN SCHEMA testpub_rf_schema2;
ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpub_rf_schema2.testpub_rf_tbl6 WHERE (i < 99);
RESET client_min_messages;
\dRp+ testpub6
- Publication testpub6
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub6
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"testpub_rf_schema2.testpub_rf_tbl6" WHERE (i < 99)
Tables from schemas:
@@ -714,10 +714,10 @@ CREATE PUBLICATION testpub_table_ins WITH (publish = 'insert, truncate');
RESET client_min_messages;
ALTER PUBLICATION testpub_table_ins ADD TABLE testpub_tbl5 (a); -- ok
\dRp+ testpub_table_ins
- Publication testpub_table_ins
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | f | f | t | f
+ Publication testpub_table_ins
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | f | f | t | f
Tables:
"public.testpub_tbl5" (a)
@@ -891,10 +891,10 @@ CREATE TABLE testpub_tbl_both_filters (a int, b int, c int, PRIMARY KEY (a,c));
ALTER TABLE testpub_tbl_both_filters REPLICA IDENTITY USING INDEX testpub_tbl_both_filters_pkey;
ALTER PUBLICATION testpub_both_filters ADD TABLE testpub_tbl_both_filters (a,c) WHERE (c != 1);
\dRp+ testpub_both_filters
- Publication testpub_both_filters
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_both_filters
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl_both_filters" (a, c) WHERE (c <> 1)
@@ -1099,10 +1099,10 @@ ERROR: relation "testpub_tbl1" is already member of publication "testpub_fortbl
CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1;
ERROR: publication "testpub_fortbl" already exists
\dRp+ testpub_fortbl
- Publication testpub_fortbl
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_fortbl
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -1140,10 +1140,10 @@ Publications:
"testpub_fortbl"
\dRp+ testpub_default
- Publication testpub_default
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | f | f
+ Publication testpub_default
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | f | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -1221,10 +1221,10 @@ REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
- Publication testpub_default
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | f | f
+ Publication testpub_default
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | f | f
(1 row)
-- fail - must be owner of publication
@@ -1234,20 +1234,20 @@ ERROR: must be owner of publication testpub_default
RESET ROLE;
ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
\dRp testpub_foo
- List of publications
- Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
--------------+--------------------------+------------+------------+---------+---------+---------+-----------+----------
- testpub_foo | regress_publication_user | f | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+-------------+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ testpub_foo | regress_publication_user | f | f | f | t | t | t | f | f
(1 row)
-- rename back to keep the rest simple
ALTER PUBLICATION testpub_foo RENAME TO testpub_default;
ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
- List of publications
- Name | Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
------------------+---------------------------+------------+------------+---------+---------+---------+-----------+----------
- testpub_default | regress_publication_user2 | f | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+-----------------+---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ testpub_default | regress_publication_user2 | f | f | f | t | t | t | f | f
(1 row)
-- adding schemas and tables
@@ -1263,19 +1263,19 @@ CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub1_forschema FOR TABLES IN SCHEMA pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
CREATE PUBLICATION testpub2_forschema FOR TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1289,44 +1289,44 @@ CREATE PUBLICATION testpub6_forschema FOR TABLES IN SCHEMA "CURRENT_SCHEMA", CUR
CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
RESET client_min_messages;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"public"
\dRp+ testpub4_forschema
- Publication testpub4_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub4_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"CURRENT_SCHEMA"
\dRp+ testpub5_forschema
- Publication testpub5_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub5_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"CURRENT_SCHEMA"
"public"
\dRp+ testpub6_forschema
- Publication testpub6_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub6_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"CURRENT_SCHEMA"
"public"
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"CURRENT_SCHEMA.CURRENT_SCHEMA"
@@ -1360,10 +1360,10 @@ ERROR: schema "testpub_view" does not exist
-- dropping the schema should reflect the change in publication
DROP SCHEMA pub_test3;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1371,20 +1371,20 @@ Tables from schemas:
-- renaming the schema should reflect the change in publication
ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1_renamed"
"pub_test2"
ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1392,10 +1392,10 @@ Tables from schemas:
-- alter publication add schema
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1404,10 +1404,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1416,10 +1416,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test1;
ERROR: schema "pub_test1" is already member of publication "testpub1_forschema"
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1427,10 +1427,10 @@ Tables from schemas:
-- alter publication drop schema
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1438,10 +1438,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
ERROR: tables from schema "pub_test2" are not part of the publication
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1449,29 +1449,29 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
-- drop all schemas
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
(1 row)
-- alter publication set multiple schema
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1480,10 +1480,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1492,10 +1492,10 @@ Tables from schemas:
-- removing the duplicate schemas
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1574,18 +1574,18 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub3_forschema;
RESET client_min_messages;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
(1 row)
ALTER PUBLICATION testpub3_forschema SET TABLES IN SCHEMA pub_test1;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1595,20 +1595,20 @@ CREATE PUBLICATION testpub_forschema_fortable FOR TABLES IN SCHEMA pub_test1, TA
CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, TABLES IN SCHEMA pub_test1;
RESET client_min_messages;
\dRp+ testpub_forschema_fortable
- Publication testpub_forschema_fortable
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_forschema_fortable
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"pub_test2.tbl1"
Tables from schemas:
"pub_test1"
\dRp+ testpub_fortable_forschema
- Publication testpub_fortable_forschema
- Owner | All tables | Table DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | t | t | t | t | f
+ Publication testpub_fortable_forschema
+ Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | t | t | t | t | f
Tables:
"pub_test2.tbl1"
Tables from schemas:
--
2.30.0.windows.2
[application/octet-stream] 0006-Deparser-and-DDL-replication-for-the-rest-2023_04_07-2.patch (378.7K, 7-0006-Deparser-and-DDL-replication-for-the-rest-2023_04_07-2.patch)
download | inline diff:
From b072482d1ebafbc6d99ebd11001f1a7c66296487 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <houzj.fnst@cn.fujitsu.com>
Date: Thu, 6 Apr 2023 20:33:02 +0800
Subject: [PATCH 6/6] Deparser and DDL replication for the rest commands.
---
contrib/test_decoding/test_decoding.c | 5 +-
doc/src/sgml/logical-replication.sgml | 211 +-
src/backend/catalog/aclchk.c | 9 +-
src/backend/catalog/pg_publication.c | 1 +
src/backend/commands/collationcmds.c | 21 +-
src/backend/commands/ddl_deparse.c | 6641 +++++++++++++++++-
src/backend/commands/event_trigger.c | 34 +
src/backend/commands/publicationcmds.c | 63 +-
src/backend/commands/seclabel.c | 6 +
src/backend/replication/logical/ddltrigger.c | 88 +-
src/backend/replication/pgoutput/pgoutput.c | 20 +-
src/backend/tcop/cmdtag.c | 10 +-
src/backend/tcop/utility.c | 4 +-
src/backend/utils/adt/regproc.c | 51 +
src/backend/utils/adt/ruleutils.c | 293 +-
src/backend/utils/cache/relcache.c | 1 +
src/bin/pg_dump/pg_dump.c | 23 +-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 12 +-
src/include/catalog/pg_publication.h | 4 +
src/include/commands/collationcmds.h | 3 +-
src/include/commands/event_trigger.h | 2 +
src/include/tcop/cmdtag.h | 2 +-
src/include/tcop/ddl_deparse.h | 2 +
src/include/tcop/deparse_utility.h | 8 +
src/include/utils/acl.h | 2 +
src/include/utils/aclchk_internal.h | 1 +
src/include/utils/builtins.h | 1 +
src/include/utils/ruleutils.h | 10 +
src/test/regress/expected/psql.out | 6 +-
src/test/regress/expected/publication.out | 420 +-
src/test/regress/expected/subscription.out | 144 +-
32 files changed, 7447 insertions(+), 652 deletions(-)
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index 763d5c007f..da5b41df59 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -774,9 +774,11 @@ pg_decode_ddl_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
appendStringInfo(ctx->out, "cmdtype: Simple, ");
break;
case DCT_TableDropStart:
+ case DCT_ObjectDropStart:
appendStringInfo(ctx->out, "cmdtype: Drop start, ");
break;
case DCT_TableDropEnd:
+ case DCT_ObjectDropEnd:
appendStringInfo(ctx->out, "cmdtype: Drop end, ");
break;
case DCT_TableAlter:
@@ -785,9 +787,6 @@ pg_decode_ddl_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
case DCT_ObjectCreate:
appendStringInfo(ctx->out, "cmdtype: Create, ");
break;
- case DCT_ObjectDrop:
- appendStringInfo(ctx->out, "cmdtype: Drop, ");
- break;
default:
appendStringInfo(ctx->out, "cmdtype: Invalid, ");
break;
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 75b395222b..10b3d5f5f1 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1480,6 +1480,11 @@ test_sub=# SELECT * FROM t1 ORDER BY id;
</para>
<itemizedlist>
+ <listitem>
+ <para>
+ all: this option enables replication of all supported DDL commands.
+ </para>
+ </listitem>
<listitem>
<para>
table: this option enables replication of Table DDL commands,
@@ -1550,22 +1555,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
<tbody>
<row>
<entry align="left"><literal>ALTER AGGREGATE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER CAST</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER COLLATION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER CONVERSION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1575,12 +1580,12 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>ALTER DEFAULT PRIVILEGES</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER DOMAIN</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1590,22 +1595,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>ALTER EXTENSION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER FOREIGN DATA WRAPPER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER FOREIGN TABLE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER FUNCTION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1615,7 +1620,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>ALTER LANGUAGE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1625,32 +1630,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>ALTER MATERIALIZED VIEW</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER OPERATOR</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER OPERATOR CLASS</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER OPERATOR FAMILY</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER POLICY</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER PROCEDURE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1665,32 +1670,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>ALTER ROUTINE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER RULER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER SCHEMA</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER SEQUENCE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER SERVER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER STATISTICS</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1715,22 +1720,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH CONFIGURATION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH DICTIONARY</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH PARSER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH TEMPLATE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1740,22 +1745,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>ALTER TRIGGER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER TYPE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER USER MAPPING</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>ALTER VIEW</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1800,7 +1805,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>COMMENT</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1825,32 +1830,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>CREATE ACCESS METHOD</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE AGGREGATE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE CAST</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE COLLATION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE CONSTRAINT</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE CONVERSION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1860,7 +1865,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>CREATE DOMAIN</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1870,22 +1875,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>CREATE EXTENSION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE FOREIGN DATA WRAPPER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE FOREIGN TABLE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE FUNCTION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1895,37 +1900,37 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>CREATE LANGUAGE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE MATERIALIZED VIEW</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE OPERATOR</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE OPERATOR CLASS</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE OPERATOR FAMILY</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE POLICY</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE PROCEDURE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1940,27 +1945,27 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>CREATE RULE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE SCHEMA</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE SEQUENCE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE SERVER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE STATISTICS</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -1985,47 +1990,47 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH CONFIGURATION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH DICTIONARY</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH PARSER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH TEMPLATE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE TRANSFORM</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE TRIGGER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE TYPE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE USER MAPPING</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>CREATE VIEW</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2055,7 +2060,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>DISCARD ALL</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2080,32 +2085,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>DROP ACCESS METHOD</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP AGGREGATE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP CAST</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP COLLATION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP CONSTRAINT</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP CONVERSION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2115,7 +2120,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>DROP DOMAIN</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2125,22 +2130,22 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>DROP EXTENSION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP FOREIGN DATA WRAPPER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP FOREIGN TABLE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP FUNCTION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2150,27 +2155,27 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>DROP LANGUAGE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP MATERIALIZED VIEW</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP OPERATOR</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP OPERATOR CLASS</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP OPERATOR FAMILY</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2180,12 +2185,12 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>DROP POLICY</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP PROCEDURE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2200,32 +2205,32 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>DROP ROUTINE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP RULE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP SCHEMA</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP SEQUENCE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP SERVER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP STATISTICS</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2245,47 +2250,47 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH CONFIGURATION</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH DICTIONARY</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH PARSER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH TEMPLATE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP TRANSFORM</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP TRIGGER</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP TYPE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP USER MAPPING</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
<entry align="left"><literal>DROP VIEW</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2305,7 +2310,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>GRANT</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2315,7 +2320,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>IMPORT FOREIGN SCHEMA</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2370,7 +2375,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>REFRESH MATERIALIZED VIEW</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2390,7 +2395,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>REVOKE</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
@@ -2415,7 +2420,7 @@ ALTER PUBLICATION mypub SET (ddl = 'table');
</row>
<row>
<entry align="left"><literal>SECURITY LABEL</literal></entry>
- <entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>X</literal></entry>
<entry align="left"></entry>
</row>
<row>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 45cdcd3dc6..4b5a3ba950 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -129,7 +129,6 @@ static void expand_all_col_privileges(Oid table_oid, Form_pg_class classForm,
AclMode *col_privileges,
int num_col_privileges);
static AclMode string_to_privilege(const char *privname);
-static const char *privilege_to_string(AclMode privilege);
static AclMode restrict_and_check_grant(bool is_grant, AclMode avail_goptions,
bool all_privs, AclMode privileges,
Oid objectId, Oid grantorId,
@@ -385,11 +384,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
ListCell *cell;
const char *errormsg;
AclMode all_privileges;
+ Oid grantor = InvalidOid;
if (stmt->grantor)
{
- Oid grantor;
-
grantor = get_rolespec_oid(stmt->grantor, false);
/*
@@ -408,6 +406,9 @@ ExecuteGrantStmt(GrantStmt *stmt)
istmt.is_grant = stmt->is_grant;
istmt.objtype = stmt->objtype;
+ /* Copy the grantor id needed for DDL deparsing of Grant */
+ istmt.grantor_uid = grantor;
+
/* Collect the OIDs of the target objects */
switch (stmt->targtype)
{
@@ -2622,7 +2623,7 @@ string_to_privilege(const char *privname)
return 0; /* appease compiler */
}
-static const char *
+const char *
privilege_to_string(AclMode privilege)
{
switch (privilege)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 1a3105e025..bb8ee605bd 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1028,6 +1028,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubupdate = pubform->pubupdate;
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
+ pub->pubactions.pubddl_all = pubform->pubddl_all;
pub->pubactions.pubddl_table = pubform->pubddl_table;
pub->pubactions.pubddl_index = pubform->pubddl_index;
pub->pubviaroot = pubform->pubviaroot;
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index c91fe66d9b..285a5dcf8a 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -50,9 +50,21 @@ typedef struct
/*
* CREATE COLLATION
+ *
+ * pstate: parse state.
+ * names: qualified collation names (a list of String).
+ * parameters: collation attributes (a list of DefElem).
+ * if_not_exists: if true, don't fail on duplicate name, just print a notice
+ * and return InvalidOid.
+ * from_existing_collid: output argument which, if not NULL, is set to the
+ * address of existing collation that was used to create in case of
+ * CREATE COLLATION any_name FROM existing_collation.
+ *
+ * If successful, returns the address of the new collation.
*/
ObjectAddress
-DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_exists)
+DefineCollation(ParseState *pstate, List *names, List *parameters,
+ bool if_not_exists, ObjectAddress *from_existing_collid)
{
char *collName;
Oid collNamespace;
@@ -143,6 +155,13 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for collation %u", collid);
+ /*
+ * Make from existing collationid available to callers for statements
+ * such as CREATE COLLATION any_name FROM existing_collation.
+ */
+ if (from_existing_collid && OidIsValid(collid))
+ ObjectAddressSet(*from_existing_collid, CollationRelationId, collid);
+
collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider;
collisdeterministic = ((Form_pg_collation) GETSTRUCT(tp))->collisdeterministic;
collencoding = ((Form_pg_collation) GETSTRUCT(tp))->collencoding;
diff --git a/src/backend/commands/ddl_deparse.c b/src/backend/commands/ddl_deparse.c
index 5d85070a85..fa4ef7b971 100644
--- a/src/backend/commands/ddl_deparse.c
+++ b/src/backend/commands/ddl_deparse.c
@@ -59,6 +59,8 @@
#include "catalog/pg_opfamily.h"
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_publication_rel.h"
+#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_sequence.h"
@@ -71,6 +73,7 @@
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "commands/sequence.h"
#include "commands/tablespace.h"
#include "foreign/foreign.h"
@@ -159,6 +162,7 @@ bool verbose = true;
static void append_array_object(ObjTree *tree, char *sub_fmt, List *array);
static void append_bool_object(ObjTree *tree, char *sub_fmt, bool value);
+static void append_float_object(ObjTree *tree, char *sub_fmt, float8 value);
static void append_null_object(ObjTree *tree, char *sub_fmt);
static void append_object_object(ObjTree *tree, char *sub_fmt, ObjTree *value);
static char *append_object_to_format_string(ObjTree *tree, char *sub_fmt);
@@ -168,11 +172,21 @@ static void append_string_object(ObjTree *tree, char *sub_fmt, char *name,
static void format_type_detailed(Oid type_oid, int32 typemod,
Oid *nspid, char **typname, char **typemodstr,
bool *typarray);
+static List *FunctionGetDefaults(text *proargdefaults);
static ObjElem *new_object(ObjType type, char *name);
static ObjTree *new_objtree_for_qualname_id(Oid classId, Oid objectId);
+static ObjTree *new_objtree_for_rolespec(RoleSpec *spec);
static ObjElem *new_object_object(ObjTree *value);
static ObjTree *new_objtree_VA(char *fmt, int numobjs,...);
+static ObjTree *new_objtree(char *fmt);
+static ObjElem *new_string_object(char *value);
static JsonbValue *objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state, char *owner);
+static void pg_get_indexdef_detailed(Oid indexrelid,
+ char **index_am,
+ char **definition,
+ char **reloptions,
+ char **tablespace,
+ char **whereClause);
static char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
List *dpcontext, List **exprs);
@@ -180,8 +194,18 @@ static ObjTree *deparse_ColumnDef(Relation relation, List *dpcontext, bool compo
ColumnDef *coldef, bool is_alter, List **exprs);
static ObjTree *deparse_ColumnIdentity(Oid seqrelid, char identity, bool alter_table);
static ObjTree *deparse_ColumnSetOptions(AlterTableCmd *subcmd);
+static ObjTree *deparse_DefineStmt_Aggregate(Oid objectId, DefineStmt *define);
+static ObjTree *deparse_DefineStmt_Collation(Oid objectId, DefineStmt *define,
+ ObjectAddress fromCollid);
+static ObjTree *deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define);
+static ObjTree *deparse_DefineStmt_Type(Oid objectId, DefineStmt *define);
+static ObjTree *deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define, ObjectAddress copied);
+static ObjTree *deparse_DefineStmt_TSParser(Oid objectId, DefineStmt *define);
+static ObjTree *deparse_DefineStmt_TSDictionary(Oid objectId, DefineStmt *define);
+static ObjTree *deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define);
static ObjTree *deparse_DefElem(DefElem *elem, bool is_reset);
+static ObjTree *deparse_FunctionSet(VariableSetKind kind, char *name, char *value);
static ObjTree *deparse_OnCommitClause(OnCommitAction option);
static ObjTree *deparse_RelSetOptions(AlterTableCmd *subcmd);
@@ -190,6 +214,7 @@ static inline ObjElem *deparse_Seq_Cycle(Form_pg_sequence seqdata, bool alter_ta
static inline ObjElem *deparse_Seq_IncrementBy(Form_pg_sequence seqdata, bool alter_table);
static inline ObjElem *deparse_Seq_Minvalue(Form_pg_sequence seqdata, bool alter_table);
static inline ObjElem *deparse_Seq_Maxvalue(Form_pg_sequence seqdata, bool alter_table);
+static ObjElem *deparse_Seq_OwnedBy(Oid sequenceId, bool alter_table);
static inline ObjElem *deparse_Seq_Restart(int64 last_value);
static inline ObjElem *deparse_Seq_Startwith(Form_pg_sequence seqdata, bool alter_table);
static inline ObjElem *deparse_Seq_As(Form_pg_sequence seqdata);
@@ -201,10 +226,15 @@ static inline ObjElem *deparse_Type_Typmod_Out(Form_pg_type typForm);
static inline ObjElem *deparse_Type_Analyze(Form_pg_type typForm);
static inline ObjElem *deparse_Type_Subscript(Form_pg_type typForm);
+static ObjTree *deparse_FdwOptions(List *options, char *colname,
+ bool altercoloptions);
+
static List *deparse_InhRelations(Oid objectId);
static List *deparse_TableElements(Relation relation, List *tableElements, List *dpcontext,
bool typed, bool composite);
+static char *DomainGetDefault(HeapTuple domTup, bool missing_ok);
+
/*
* Append present as false to a tree.
*/
@@ -214,6 +244,42 @@ append_not_present(ObjTree *tree)
append_bool_object(tree, "present", false);
}
+/*
+ * Append an int32 parameter to a tree.
+ */
+static void
+append_int_object(ObjTree *tree, char *sub_fmt, int32 value)
+{
+ ObjElem *param;
+ char *object_name;
+
+ Assert(sub_fmt);
+
+ object_name = append_object_to_format_string(tree, sub_fmt);
+
+ param = new_object(ObjTypeInteger, object_name);
+ param->value.flt = value;
+ append_premade_object(tree, param);
+}
+
+/*
+ * Append a float8 parameter to a tree.
+ */
+static void
+append_float_object(ObjTree *tree, char *sub_fmt, float8 value)
+{
+ ObjElem *param;
+ char *object_name;
+
+ Assert(sub_fmt);
+
+ object_name = append_object_to_format_string(tree, sub_fmt);
+
+ param = new_object(ObjTypeFloat, object_name);
+ param->value.flt = value;
+ append_premade_object(tree, param);
+}
+
/*
* Append an array parameter to a tree.
*/
@@ -420,6 +486,34 @@ append_string_object(ObjTree *tree, char *sub_fmt, char * object_name,
append_premade_object(tree, param);
}
+/*
+ * Append a NULL-or-quoted-literal clause. Useful for COMMENT and SECURITY
+ * LABEL.
+ *
+ * Verbose syntax
+ * %{null}s %{literal}s
+ */
+static void
+append_literal_or_null(ObjTree *parent, char *elemname, char *value)
+{
+ ObjTree *top;
+ ObjTree *part;
+
+ top = new_objtree("");
+ part = new_objtree_VA("NULL", 1,
+ "present", ObjTypeBool, !value);
+ append_object_object(top, "%{null}s", part);
+
+ part = new_objtree_VA("", 1,
+ "present", ObjTypeBool, value != NULL);
+
+ if (value)
+ append_string_object(part, "%{value}L", "value", value);
+ append_object_object(top, "%{literal}s", part);
+
+ append_object_object(parent, elemname, top);
+}
+
/*
* Similar to format_type_extended, except we return each bit of information
* separately:
@@ -522,6 +616,31 @@ format_type_detailed(Oid type_oid, int32 typemod,
ReleaseSysCache(tuple);
}
+/*
+ * Return the default values of arguments to a function, as a list of
+ * deparsed expressions.
+ */
+static List *
+FunctionGetDefaults(text *proargdefaults)
+{
+ List *nodedefs;
+ List *strdefs = NIL;
+ ListCell *cell;
+
+ nodedefs = (List *) stringToNode(text_to_cstring(proargdefaults));
+ if (!IsA(nodedefs, List))
+ elog(ERROR, "proargdefaults is not a list");
+
+ foreach(cell, nodedefs)
+ {
+ Node *onedef = lfirst(cell);
+
+ strdefs = lappend(strdefs, deparse_expression(onedef, NIL, false, false));
+ }
+
+ return strdefs;
+}
+
/*
* Return the string representation of the given RELPERSISTENCE value.
*/
@@ -711,6 +830,57 @@ new_objtree_for_type(Oid typeId, int32 typmod)
"typarray", ObjTypeBool, type_array);
}
+/*
+ * Helper routine for %{}R objects, with role specified by RoleSpec node.
+ * Special values such as ROLESPEC_CURRENT_USER are expanded to their final
+ * names.
+ */
+static ObjTree *
+new_objtree_for_rolespec(RoleSpec *spec)
+{
+ char *roletype;
+
+ if (spec->roletype != ROLESPEC_PUBLIC)
+ roletype = get_rolespec_name(spec);
+ else
+ roletype = pstrdup("");
+
+ return new_objtree_VA(NULL, 2,
+ "is_public", ObjTypeBool, spec->roletype == ROLESPEC_PUBLIC,
+ "rolename", ObjTypeString, roletype);
+}
+
+/*
+ * Helper routine for %{}R objects, with role specified by OID. (ACL_ID_PUBLIC
+ * means to use "public").
+ */
+static ObjTree *
+new_objtree_for_role_id(Oid roleoid)
+{
+ ObjTree *role;
+
+ if (roleoid != ACL_ID_PUBLIC)
+ {
+ HeapTuple roltup;
+ char *role_name;
+
+ roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleoid));
+ if (!HeapTupleIsValid(roltup))
+ elog(ERROR, "cache lookup failed for role with OID %u", roleoid);
+
+ role_name = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname);
+ role = new_objtree_VA("%{rolename}I", 1,
+ "rolename", ObjTypeString, pstrdup(role_name));
+
+ ReleaseSysCache(roltup);
+ }
+ else
+ role = new_objtree_VA("%{rolename}I", 1,
+ "rolename", ObjTypeString, "public");
+
+ return role;
+}
+
/*
* Allocate a new object tree to store parameter values -- varargs version.
*
@@ -788,6 +958,22 @@ new_objtree_VA(char *fmt, int numobjs,...)
return tree;
}
+/*
+ * Allocate a new string object.
+ */
+static ObjElem *
+new_string_object(char *value)
+{
+ ObjElem *param;
+
+ Assert(value);
+
+ param = new_object(ObjTypeString, NULL);
+ param->value.string = value;
+
+ return param;
+}
+
/*
* Process the pre-built format string from the ObjTree into the output parse
* state.
@@ -1950,6 +2136,83 @@ deparse_Seq_Minvalue(Form_pg_sequence seqdata, bool alter_table)
return new_object_object(ret);
}
+/*
+ * Deparse the sequence OWNED BY command.
+ *
+ * Verbose syntax
+ * OWNED BY %{owner}D
+ */
+static ObjElem *
+deparse_Seq_OwnedBy(Oid sequenceId, bool alter_table)
+{
+ ObjTree *ret = NULL;
+ Relation depRel;
+ SysScanDesc scan;
+ ScanKeyData keys[3];
+ HeapTuple tuple;
+
+ 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;
+ ObjTree *tmp_obj;
+ 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);
+ if (colname == NULL)
+ continue;
+
+ tmp_obj = new_objtree_for_qualname_id(RelationRelationId, ownerId);
+ append_string_object(tmp_obj, "attrname", "attrname", colname);
+ ret = new_objtree_VA("OWNED BY %{owner}D", 2,
+ "clause", ObjTypeString, "owned",
+ "owner", ObjTypeObject, tmp_obj);
+ }
+
+ systable_endscan(scan);
+ relation_close(depRel, AccessShareLock);
+
+ /*
+ * If there's no owner column, emit an empty OWNED BY element, set up so
+ * that it won't print anything.
+ */
+ if (!ret)
+ /* XXX this shouldn't happen ... */
+ ret = new_objtree_VA("OWNED BY %{owner}D", 3,
+ "clause", ObjTypeString, "owned",
+ "owner", ObjTypeNull,
+ "present", ObjTypeBool, false);
+
+ return new_object_object(ret);
+}
+
/*
* Deparse the sequence RESTART option.
*
@@ -2214,6 +2477,64 @@ deparse_TableElements(Relation relation, List *tableElements, List *dpcontext,
return elements;
}
+/*
+ * Deparse a CreateSeqStmt.
+ *
+ * Given a sequence OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE %{persistence}s SEQUENCE %{identity}D %{definition: }s
+ */
+static ObjTree *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+ ObjTree *ret;
+ Relation relation;
+ List *elems = NIL;
+ Form_pg_sequence seqform;
+ Sequence_values *seqvalues;
+ CreateSeqStmt *createSeqStmt = (CreateSeqStmt *) parsetree;
+
+ /*
+ * Sequence for IDENTITY COLUMN output separately (via CREATE TABLE or
+ * ALTER TABLE); return empty here.
+ */
+ if (createSeqStmt->for_identity)
+ return NULL;
+
+ seqvalues = get_sequence_values(objectId);
+ seqform = seqvalues->seqform;
+
+ /* Definition elements */
+ elems = lappend(elems, deparse_Seq_Cache(seqform, false));
+ elems = lappend(elems, deparse_Seq_Cycle(seqform, false));
+ elems = lappend(elems, deparse_Seq_IncrementBy(seqform, false));
+ elems = lappend(elems, deparse_Seq_Minvalue(seqform, false));
+ elems = lappend(elems, deparse_Seq_Maxvalue(seqform, false));
+ elems = lappend(elems, deparse_Seq_Startwith(seqform, false));
+ elems = lappend(elems, deparse_Seq_Restart(seqvalues->last_value));
+ elems = lappend(elems, deparse_Seq_As(seqform));
+
+ /* We purposefully do not emit OWNED BY here */
+
+ relation = relation_open(objectId, AccessShareLock);
+
+ ret = new_objtree_VA("CREATE %{persistence}s SEQUENCE %{if_not_exists}s %{identity}D %{definition: }s", 4,
+ "persistence", ObjTypeString,
+ get_persistence_str(relation->rd_rel->relpersistence),
+ "if_not_exists", ObjTypeString,
+ createSeqStmt->if_not_exists ? "IF NOT EXISTS" : "",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(relation->rd_rel->relnamespace,
+ RelationGetRelationName(relation)),
+ "definition", ObjTypeArray, elems);
+
+ relation_close(relation, AccessShareLock);
+
+ return ret;
+}
+
/*
* Deparse an IndexStmt.
*
@@ -2559,7 +2880,7 @@ deparse_AlterRelation(CollectedCommand *cmd)
Assert(cmd->type == SCT_AlterTable);
stmt = (AlterTableStmt *) cmd->parsetree;
- Assert(IsA(stmt, AlterTableStmt));
+ Assert(IsA(stmt, AlterTableStmt) || IsA(stmt, ViewStmt));
/*
* ALTER TABLE subcommands generated for TableLikeClause is processed in
@@ -2602,8 +2923,10 @@ deparse_AlterRelation(CollectedCommand *cmd)
elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
}
- ret = new_objtree_VA("ALTER %{objtype}s %{identity}D", 2,
+ ret = new_objtree_VA("ALTER %{objtype}s %{identity}D", 3,
"objtype", ObjTypeString, reltype,
+ "only", ObjTypeString,
+ stmt->relation->inh ? "" : "ONLY",
"identity", ObjTypeObject,
new_objtree_for_qualname(rel->rd_rel->relnamespace,
RelationGetRelationName(rel)));
@@ -3035,13 +3358,12 @@ deparse_AlterRelation(CollectedCommand *cmd)
}
break;
-#ifdef TODOLIST
case AT_AlterColumnGenericOptions:
tmp_obj = deparse_FdwOptions((List *) subcmd->def,
- subcmd->name);
+ subcmd->name, true);
subcmds = lappend(subcmds, new_object_object(tmp_obj));
break;
-#endif
+
case AT_ChangeOwner:
tmp_obj = new_objtree_VA("OWNER TO %{owner}I", 2,
"type", ObjTypeString, "change owner",
@@ -3432,6 +3754,200 @@ deparse_drop_command(const char *objidentity, const char *objecttype,
return command;
}
+/*
+ * Deparse an AlterCollationStmt (ALTER COLLATION)
+ *
+ * Given a collation OID and the parse tree that created it, return an ObjTree
+ * representing the alter command.
+ *
+ * Verbose syntax:
+ * ALTER COLLATION %{identity}O REFRESH VERSION
+ */
+static ObjTree *
+deparse_AlterCollation(Oid objectId, Node *parsetree)
+{
+ ObjTree *ret;
+ HeapTuple colTup;
+ Form_pg_collation colForm;
+
+ colTup = SearchSysCache1(COLLOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(colTup))
+ elog(ERROR, "cache lookup failed for collation with OID %u", objectId);
+ colForm = (Form_pg_collation) GETSTRUCT(colTup);
+
+ ret = new_objtree_VA("ALTER COLLATION %{identity}O REFRESH VERSION", 1,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(colForm->collnamespace,
+ NameStr(colForm->collname)));
+
+ ReleaseSysCache(colTup);
+
+ return ret;
+}
+
+/*
+ * Handle deparsing setting of Function
+ *
+ * Verbose syntax
+ * RESET ALL
+ * OR
+ * SET %{set_name}I TO %{set_value}s
+ * OR
+ * RESET %{set_name}I
+ */
+static ObjTree *
+deparse_FunctionSet(VariableSetKind kind, char *name, char *value)
+{
+ ObjTree *ret;
+
+ if (kind == VAR_RESET_ALL)
+ ret = new_objtree("RESET ALL");
+ else if (kind == VAR_SET_VALUE)
+ {
+ ret = new_objtree_VA("SET %{set_name}I", 1,
+ "set_name", ObjTypeString, name);
+
+ /*
+ * Some GUC variable names are 'LIST' type and hence must not be
+ * quoted.
+ */
+ if (GetConfigOptionFlags(name, true) & GUC_LIST_QUOTE)
+ append_string_object(ret, "TO %{set_value}s", "set_value", value);
+ else
+ append_string_object(ret, "TO %{set_value}L", "set_value", value);
+ }
+ else
+ ret = new_objtree_VA("RESET %{set_name}I", 1,
+ "set_name", ObjTypeString, name);
+
+ return ret;
+}
+
+/*
+ * Deparse an AlterFunctionStmt (ALTER FUNCTION/ROUTINE/PROCEDURE)
+ *
+ * Given a function OID and the parse tree that created it, return an ObjTree
+ * representing the alter command.
+ *
+ * Verbose syntax:
+ * ALTER FUNCTION/ROUTINE/PROCEDURE %{signature}s %{definition: }s
+ */
+static ObjTree *
+deparse_AlterFunction(Oid objectId, Node *parsetree)
+{
+ AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree;
+ ObjTree *ret;
+ ObjTree *sign;
+ HeapTuple procTup;
+ Form_pg_proc procForm;
+ List *params = NIL;
+ List *elems = NIL;
+ ListCell *cell;
+ int i;
+
+ /* Get the pg_proc tuple */
+ procTup = SearchSysCache1(PROCOID, objectId);
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function with OID %u", objectId);
+ procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+ if (procForm->prokind == PROKIND_PROCEDURE)
+ ret = new_objtree("ALTER PROCEDURE");
+ else
+ ret = new_objtree(node->objtype == OBJECT_ROUTINE ?
+ "ALTER ROUTINE" : "ALTER FUNCTION");
+
+ /*
+ * ALTER FUNCTION does not change signature so we can use catalog to get
+ * input type Oids.
+ */
+ for (i = 0; i < procForm->pronargs; i++)
+ {
+ ObjTree *tmp_obj;
+
+ tmp_obj = new_objtree_VA("%{type}T", 1,
+ "type", ObjTypeObject,
+ new_objtree_for_type(procForm->proargtypes.values[i], -1));
+ params = lappend(params, new_object_object(tmp_obj));
+ }
+
+ sign = new_objtree_VA("%{identity}D (%{arguments:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId, objectId),
+ "arguments", ObjTypeArray, params);
+
+ append_object_object(ret, "%{signature}s", sign);
+
+ foreach(cell, node->actions)
+ {
+ DefElem *defel = (DefElem *) lfirst(cell);
+ ObjTree *tmp_obj = NULL;
+
+ if (strcmp(defel->defname, "volatility") == 0)
+ tmp_obj = new_objtree(strVal(defel->arg));
+ else if (strcmp(defel->defname, "strict") == 0)
+ tmp_obj = new_objtree(boolVal(defel->arg) ?
+ "RETURNS NULL ON NULL INPUT" :
+ "CALLED ON NULL INPUT");
+ else if (strcmp(defel->defname, "security") == 0)
+ tmp_obj = new_objtree(boolVal(defel->arg) ?
+ "SECURITY DEFINER" : "SECURITY INVOKER");
+ else if (strcmp(defel->defname, "leakproof") == 0)
+ tmp_obj = new_objtree(boolVal(defel->arg) ?
+ "LEAKPROOF" : "NOT LEAKPROOF");
+ else if (strcmp(defel->defname, "cost") == 0)
+ tmp_obj = new_objtree_VA("COST %{cost}n", 1,
+ "cost", ObjTypeFloat,
+ defGetNumeric(defel));
+ else if (strcmp(defel->defname, "rows") == 0)
+ {
+ tmp_obj = new_objtree("ROWS");
+ if (defGetNumeric(defel) == 0)
+ append_not_present(tmp_obj);
+ else
+ append_float_object(tmp_obj, "%{rows}n",
+ defGetNumeric(defel));
+ }
+ else if (strcmp(defel->defname, "set") == 0)
+ {
+ VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+ char *value = ExtractSetVariableArgs(sstmt);
+
+ tmp_obj = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+ }
+ else if (strcmp(defel->defname, "support") == 0)
+ {
+ Oid argtypes[1];
+
+ tmp_obj = new_objtree("SUPPORT");
+
+ Assert(procForm->prosupport);
+
+ /*
+ * We should qualify the support function's name if it wouldn't be
+ * resolved by lookup in the current search path.
+ */
+ argtypes[0] = INTERNALOID;
+ append_string_object(tmp_obj, "%{name}s", "name",
+ generate_function_name(procForm->prosupport, 1,
+ NIL, argtypes,
+ false, NULL,
+ EXPR_KIND_NONE));
+ }
+ else if (strcmp(defel->defname, "parallel") == 0)
+ tmp_obj = new_objtree_VA("PARALLEL %{value}s", 1,
+ "value", ObjTypeString, strVal(defel->arg));
+
+ elems = lappend(elems, new_object_object(tmp_obj));
+ }
+
+ append_array_object(ret, "%{definition: }s", elems);
+
+ ReleaseSysCache(procTup);
+
+ return ret;
+}
+
/*
* Deparse an AlterObjectSchemaStmt (ALTER ... SET SCHEMA command)
*
@@ -3477,22 +3993,499 @@ deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
}
/*
- * Deparse an AlterOwnerStmt (ALTER ... OWNER TO ...).
- *
- * Given the object address and the parse tree that created it, return an
- * ObjTree representing the alter command.
+ * Deparse a GRANT/REVOKE command.
*
* Verbose syntax
- * ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I
+ * GRANT %{privileges:, }s ON" %{objtype}s %{privtarget:, }s TO %{grantees:, }s
+ * %{grant_option}s GRANTED BY %{rolename}s
+ * OR
+ * REVOKE %{privileges:, }s ON" %{objtype}s %{privtarget:, }s
+ * FROM %{grantees:, }s %{grant_option}s %{cascade}s
*/
static ObjTree *
-deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree)
+deparse_GrantStmt(CollectedCommand *cmd)
{
- AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+ InternalGrant *istmt;
+ ObjTree *ret;
+ List *list = NIL;
+ ListCell *cell;
+ Oid classId;
+ ObjTree *tmp;
- return new_objtree_VA("ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I", 3,
- "objtype", ObjTypeString,
- stringify_objtype(node->objectType, false),
+ /* Don't deparse SQL commands generated while creating extension */
+ if (cmd->in_extension)
+ return NULL;
+
+ istmt = cmd->d.grant.istmt;
+
+ /*
+ * If there are no objects from "ALL ... IN SCHEMA" to be granted, then
+ * nothing to do.
+ */
+ if (istmt->objects == NIL)
+ return NULL;
+
+ switch (istmt->objtype)
+ {
+ case OBJECT_COLUMN:
+ case OBJECT_TABLE:
+ case OBJECT_SEQUENCE:
+ classId = RelationRelationId;
+ break;
+ case OBJECT_DOMAIN:
+ case OBJECT_TYPE:
+ classId = TypeRelationId;
+ break;
+ case OBJECT_FDW:
+ classId = ForeignDataWrapperRelationId;
+ break;
+ case OBJECT_FOREIGN_SERVER:
+ classId = ForeignServerRelationId;
+ break;
+ case OBJECT_FUNCTION:
+ case OBJECT_PROCEDURE:
+ case OBJECT_ROUTINE:
+ classId = ProcedureRelationId;
+ break;
+ case OBJECT_LANGUAGE:
+ classId = LanguageRelationId;
+ break;
+ case OBJECT_LARGEOBJECT:
+ classId = LargeObjectRelationId;
+ break;
+ case OBJECT_SCHEMA:
+ classId = NamespaceRelationId;
+ break;
+ case OBJECT_DATABASE:
+ case OBJECT_TABLESPACE:
+ classId = InvalidOid;
+ elog(ERROR, "global objects not supported");
+ break;
+ default:
+ elog(ERROR, "invalid OBJECT value %d", istmt->objtype);
+ }
+
+ /* GRANT TO or REVOKE FROM */
+ ret = new_objtree(istmt->is_grant ? "GRANT" : "REVOKE");
+
+ /* Build a list of privileges to grant/revoke */
+ if (istmt->all_privs)
+ {
+ tmp = new_objtree("ALL PRIVILEGES");
+ list = list_make1(new_object_object(tmp));
+ }
+ else
+ {
+ char *priv;
+ if (istmt->privileges & ACL_INSERT)
+ {
+ priv = (char *)privilege_to_string(ACL_INSERT);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_SELECT)
+ {
+ priv = (char *)privilege_to_string(ACL_SELECT);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_UPDATE)
+ {
+ priv = (char *)privilege_to_string(ACL_UPDATE);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_DELETE)
+ {
+ priv = (char *)privilege_to_string(ACL_DELETE);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_TRUNCATE)
+ {
+ priv = (char *)privilege_to_string(ACL_TRUNCATE);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_REFERENCES)
+ {
+ priv = (char *)privilege_to_string(ACL_REFERENCES);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_TRIGGER)
+ {
+ priv = (char *)privilege_to_string(ACL_TRIGGER);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_EXECUTE)
+ {
+ priv = (char *)privilege_to_string(ACL_EXECUTE);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_USAGE)
+ {
+ priv = (char *)privilege_to_string(ACL_USAGE);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_CREATE)
+ {
+ priv = (char *)privilege_to_string(ACL_CREATE);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_CREATE_TEMP)
+ {
+ priv = (char *)privilege_to_string(ACL_CREATE_TEMP);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_CONNECT)
+ {
+ priv = (char *)privilege_to_string(ACL_CONNECT);
+ list = lappend(list, new_string_object(priv));
+ }
+ if (istmt->privileges & ACL_MAINTAIN)
+ {
+ priv = (char *)privilege_to_string(ACL_MAINTAIN);
+ list = lappend(list, new_string_object(priv));
+ }
+
+ if (!istmt->is_grant && istmt->grant_option)
+ append_string_object(ret, "%{grant_option}s", "grant_option",
+ istmt->grant_option ? "GRANT OPTION FOR" : "");
+
+ if (istmt->col_privs != NIL)
+ {
+ ListCell *ocell;
+
+ foreach(ocell, istmt->col_privs)
+ {
+ AccessPriv *priv = lfirst(ocell);
+ List *cols = NIL;
+
+ foreach(cell, priv->cols)
+ {
+ String *colname = lfirst_node(String, cell);
+
+ cols = lappend(cols,
+ new_string_object(strVal(colname)));
+ }
+
+ tmp = new_objtree_VA("(%{cols:, }s) %{priv}s", 2,
+ "cols", ObjTypeArray, cols,
+ "priv", ObjTypeString,
+ priv->priv_name ? priv->priv_name : "ALL PRIVILEGES");
+
+ list = lappend(list, new_object_object(tmp));
+ }
+ }
+ }
+ append_array_object(ret, "%{privileges:, }s ON", list);
+
+ append_string_object(ret, "%{objtype}s", "objtype",
+ (char *)stringify_objtype(istmt->objtype, true));
+
+ /* Target objects. We use object identities here */
+ list = NIL;
+ foreach(cell, istmt->objects)
+ {
+ Oid objid = lfirst_oid(cell);
+ ObjectAddress addr;
+
+ addr.classId = classId;
+ addr.objectId = objid;
+ addr.objectSubId = 0;
+
+ tmp = new_objtree_VA("%{identity}s", 1,
+ "identity", ObjTypeString,
+ getObjectIdentity(&addr, false));
+
+ list = lappend(list, new_object_object(tmp));
+ }
+ append_array_object(ret, "%{privtarget:, }s", list);
+
+ append_format_string(ret, istmt->is_grant ? "TO" : "FROM");
+
+ /* List of grantees */
+ list = NIL;
+ foreach(cell, istmt->grantees)
+ {
+ Oid grantee = lfirst_oid(cell);
+
+ tmp = new_objtree_for_role_id(grantee);
+ list = lappend(list, new_object_object(tmp));
+ }
+
+ append_array_object(ret, "%{grantees:, }s", list);
+
+ /* The wording of the grant option is variable ... */
+ if (istmt->is_grant)
+ append_string_object(ret, "%{grant_option}s", "grant_option",
+ istmt->grant_option ? "WITH GRANT OPTION" : "");
+
+ if (istmt->grantor_uid)
+ {
+ HeapTuple roltup;
+ char *rolename;
+
+ roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(istmt->grantor_uid));
+ if (!HeapTupleIsValid(roltup))
+ elog(ERROR, "cache lookup failed for role with OID %u",
+ istmt->grantor_uid);
+
+ rolename = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname);
+ append_string_object(ret, "GRANTED BY %{rolename}s",
+ "rolename", rolename);
+ ReleaseSysCache(roltup);
+ }
+
+ if (!istmt->is_grant)
+ append_string_object(ret, "%{cascade}s", "cascade",
+ istmt->behavior == DROP_CASCADE ? "CASCADE" : "");
+
+ return ret;
+}
+
+/*
+ * Deparse an AlterOperatorStmt (ALTER OPERATOR ... SET ...).
+ *
+ * Given an operator OID and the parse tree that created it, return an ObjTree
+ * representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER OPERATOR %{identity}O (%{left_type}T, %{right_type}T)
+ * SET (%{elems:, }s)
+ */
+static ObjTree *
+deparse_AlterOperatorStmt(Oid objectId, Node *parsetree)
+{
+ HeapTuple oprTup;
+ AlterOperatorStmt *node = (AlterOperatorStmt *) parsetree;
+ ObjTree *ret;
+ Form_pg_operator oprForm;
+ ListCell *cell;
+ List *list = NIL;
+
+ oprTup = SearchSysCache1(OPEROID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(oprTup))
+ elog(ERROR, "cache lookup failed for operator with OID %u", objectId);
+ oprForm = (Form_pg_operator) GETSTRUCT(oprTup);
+
+ ret = new_objtree_VA("ALTER OPERATOR %{identity}O", 1,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(oprForm->oprnamespace,
+ NameStr(oprForm->oprname)));
+
+ /* LEFTARG */
+ if (OidIsValid(oprForm->oprleft))
+ append_object_object(ret, "(%{left_type}T",
+ new_objtree_for_type(oprForm->oprleft, -1));
+ else
+ append_string_object(ret, "(%{left_type}s", "left_type", "NONE");
+
+ /* RIGHTARG */
+ Assert(OidIsValid(oprForm->oprleft));
+ append_object_object(ret, ", %{right_type}T)",
+ new_objtree_for_type(oprForm->oprright, -1));
+
+ foreach(cell, node->options)
+ {
+ DefElem *elem = (DefElem *) lfirst(cell);
+ ObjTree *tmp_obj = NULL;
+
+ if (strcmp(elem->defname, "restrict") == 0)
+ {
+ tmp_obj = new_objtree_VA("RESTRICT=", 1,
+ "clause", ObjTypeString, "restrict");
+ if (OidIsValid(oprForm->oprrest))
+ append_object_object(tmp_obj, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ oprForm->oprrest));
+ else
+ append_string_object(tmp_obj, "%{procedure}s", "procedure",
+ "NONE");
+ }
+ else if (strcmp(elem->defname, "join") == 0)
+ {
+ tmp_obj = new_objtree_VA("JOIN=", 1,
+ "clause", ObjTypeString, "join");
+ if (OidIsValid(oprForm->oprjoin))
+ append_object_object(tmp_obj, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ oprForm->oprjoin));
+ else
+ append_string_object(tmp_obj, "%{procedure}s", "procedure",
+ "NONE");
+ }
+
+ Assert(tmp_obj);
+ list = lappend(list, new_object_object(tmp_obj));
+ }
+
+ append_array_object(ret, "SET (%{elems:, }s)", list);
+
+ ReleaseSysCache(oprTup);
+
+ return ret;
+}
+
+/*
+ * Deparse an ALTER OPERATOR FAMILY ADD/DROP command.
+ *
+ * Given the CollectedCommand, return an ObjTree representing the Alter
+ * Operator command.
+ *
+ * Verbose syntax
+ * ALTER OPERATOR FAMILY %{identity}D USING %{amname}I ADD/DROP %{items:,}s
+ */
+static ObjTree *
+deparse_AlterOpFamily(CollectedCommand *cmd)
+{
+ ObjTree *ret;
+ AlterOpFamilyStmt *stmt = (AlterOpFamilyStmt *) cmd->parsetree;
+ HeapTuple ftp;
+ Form_pg_opfamily opfForm;
+ List *list = NIL;
+ ListCell *cell;
+
+ /* Don't deparse SQL commands generated while creating extension */
+ if (cmd->in_extension)
+ return NULL;
+
+ ftp = SearchSysCache1(OPFAMILYOID,
+ ObjectIdGetDatum(cmd->d.opfam.address.objectId));
+ if (!HeapTupleIsValid(ftp))
+ elog(ERROR, "cache lookup failed for operator family with OID %u",
+ cmd->d.opfam.address.objectId);
+ opfForm = (Form_pg_opfamily) GETSTRUCT(ftp);
+
+ ret = new_objtree_VA("ALTER OPERATOR FAMILY %{identity}D USING %{amname}I", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(opfForm->opfnamespace,
+ NameStr(opfForm->opfname)),
+ "amname", ObjTypeString, stmt->amname);
+
+ foreach(cell, cmd->d.opfam.operators)
+ {
+ OpFamilyMember *oper = lfirst(cell);
+ ObjTree *tmp_obj;
+
+ /*
+ * Verbose syntax
+ *
+ * OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T) %{purpose}s
+ */
+ tmp_obj = new_objtree_VA("OPERATOR %{num}n", 1,
+ "num", ObjTypeInteger, oper->number);
+
+ /* Add the operator name; the DROP case doesn't have this */
+ if (!stmt->isDrop)
+ append_object_object(tmp_obj, "%{operator}O",
+ new_objtree_for_qualname_id(OperatorRelationId,
+ oper->object));
+
+ /* Add the types */
+ append_object_object(tmp_obj, "(%{ltype}T,",
+ new_objtree_for_type(oper->lefttype, -1));
+ append_object_object(tmp_obj, "%{rtype}T)",
+ new_objtree_for_type(oper->righttype, -1));
+
+ /* Add the FOR SEARCH / FOR ORDER BY clause; not in the DROP case */
+ if (!stmt->isDrop)
+ {
+ if (oper->sortfamily == InvalidOid)
+ append_string_object(tmp_obj, "%{purpose}s", "purpose",
+ "FOR SEARCH");
+ else
+ {
+ ObjTree *orderby_obj;
+
+ orderby_obj = new_objtree_VA("FOR ORDER BY %{opfamily}D", 1,
+ "opfamily", ObjTypeObject,
+ new_objtree_for_qualname_id(OperatorFamilyRelationId,
+ oper->sortfamily));
+ append_object_object(tmp_obj, "%{purpose}s", orderby_obj);
+ }
+ }
+
+ list = lappend(list, new_object_object(tmp_obj));
+ }
+
+ foreach(cell, cmd->d.opfam.procedures)
+ {
+ OpFamilyMember *proc = lfirst(cell);
+ ObjTree *tmp_obj;
+
+ tmp_obj = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T)", 3,
+ "num", ObjTypeInteger, proc->number,
+ "ltype", ObjTypeObject,
+ new_objtree_for_type(proc->lefttype, -1),
+ "rtype", ObjTypeObject,
+ new_objtree_for_type(proc->righttype, -1));
+
+ /*
+ * Add the function name and arg types; the DROP case doesn't have
+ * this
+ */
+ if (!stmt->isDrop)
+ {
+ HeapTuple procTup;
+ Form_pg_proc procForm;
+ Oid *proargtypes;
+ List *arglist = NIL;
+ int i;
+
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for procedure with OID %u",
+ proc->object);
+ procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+ proargtypes = procForm->proargtypes.values;
+ for (i = 0; i < procForm->pronargs; i++)
+ {
+ ObjTree *arg;
+
+ arg = new_objtree_for_type(proargtypes[i], -1);
+ arglist = lappend(arglist, new_object_object(arg));
+ }
+
+ append_object_object(tmp_obj, "%{function}D",
+ new_objtree_for_qualname(procForm->pronamespace,
+ NameStr(procForm->proname)));
+
+ append_format_string(tmp_obj, "(");
+ append_array_object(tmp_obj, "%{argtypes:, }T", arglist);
+ append_format_string(tmp_obj, ")");
+
+ ReleaseSysCache(procTup);
+ }
+
+ list = lappend(list, new_object_object(tmp_obj));
+ }
+
+ if (stmt->isDrop)
+ append_format_string(ret, "DROP");
+ else
+ append_format_string(ret, "ADD");
+
+ append_array_object(ret, "%{items:, }s", list);
+
+ ReleaseSysCache(ftp);
+
+ return ret;
+}
+
+/*
+ * Deparse an AlterOwnerStmt (ALTER ... OWNER TO ...).
+ *
+ * Given the object address and the parse tree that created it, return an
+ * ObjTree representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I
+ */
+static ObjTree *
+deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree)
+{
+ AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+
+ return new_objtree_VA("ALTER %{objtype}s %{identity}s OWNER TO %{newowner}I", 3,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->objectType, false),
"identity", ObjTypeString,
getObjectIdentity(&address, false),
"newowner", ObjTypeString,
@@ -3500,148 +4493,5385 @@ deparse_AlterOwnerStmt(ObjectAddress address, Node *parsetree)
}
/*
- * Deparse a RenameStmt.
+ * Deparse an AlterSeqStmt.
+ *
+ * Given a sequence OID and a parse tree that modified it, return an ObjTree
+ * representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER SEQUENCE %{identity}D %{definition: }s
+ */
+static ObjTree *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+ ObjTree *ret;
+ Relation relation;
+ List *elems = NIL;
+ ListCell *cell;
+ Form_pg_sequence seqform;
+ Sequence_values *seqvalues;
+ AlterSeqStmt *alterSeqStmt = (AlterSeqStmt *) parsetree;
+
+ /*
+ * Sequence for IDENTITY COLUMN output separately (via CREATE TABLE or
+ * ALTER TABLE); return empty here.
+ */
+ if (alterSeqStmt->for_identity)
+ return NULL;
+
+ seqvalues = get_sequence_values(objectId);
+ seqform = seqvalues->seqform;
+
+ foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+ {
+ DefElem *elem = (DefElem *) lfirst(cell);
+ ObjElem *newelm;
+
+ if (strcmp(elem->defname, "cache") == 0)
+ newelm = deparse_Seq_Cache(seqform, false);
+ else if (strcmp(elem->defname, "cycle") == 0)
+ newelm = deparse_Seq_Cycle(seqform, false);
+ else if (strcmp(elem->defname, "increment") == 0)
+ newelm = deparse_Seq_IncrementBy(seqform, false);
+ else if (strcmp(elem->defname, "minvalue") == 0)
+ newelm = deparse_Seq_Minvalue(seqform, false);
+ else if (strcmp(elem->defname, "maxvalue") == 0)
+ newelm = deparse_Seq_Maxvalue(seqform, false);
+ else if (strcmp(elem->defname, "start") == 0)
+ newelm = deparse_Seq_Startwith(seqform, false);
+ else if (strcmp(elem->defname, "restart") == 0)
+ newelm = deparse_Seq_Restart(seqvalues->last_value);
+ else if (strcmp(elem->defname, "owned_by") == 0)
+ newelm = deparse_Seq_OwnedBy(objectId, false);
+ else if (strcmp(elem->defname, "as") == 0)
+ newelm = deparse_Seq_As(seqform);
+ else
+ elog(ERROR, "invalid sequence option %s", elem->defname);
+
+ elems = lappend(elems, newelm);
+ }
+
+ relation = relation_open(objectId, AccessShareLock);
+
+ ret = new_objtree_VA("ALTER SEQUENCE %{identity}D %{definition: }s", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(relation->rd_rel->relnamespace,
+ RelationGetRelationName(relation)),
+ "definition", ObjTypeArray, elems);
+
+ relation_close(relation, AccessShareLock);
+
+ return ret;
+}
+
+/*
+ * Deparse an AlterTypeStmt.
+ *
+ * Given a type OID and a parse tree that modified it, return an ObjTree
+ * representing the alter type.
+ *
+ * Verbose syntax
+ * ALTER TYPE %{identity}D SET (%{definition: }s)
+ */
+static ObjTree *
+deparse_AlterTypeSetStmt(Oid objectId, Node *cmd)
+{
+ AlterTypeStmt *alterTypeSetStmt = (AlterTypeStmt *) cmd;
+ ListCell *pl;
+ List *elems = NIL;
+ HeapTuple typTup;
+ Form_pg_type typForm;
+
+ typTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(typTup))
+ elog(ERROR, "cache lookup failed for type with OID %u", objectId);
+ typForm = (Form_pg_type) GETSTRUCT(typTup);
+
+ foreach(pl, alterTypeSetStmt->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+ ObjElem *newelm;
+
+ if (strcmp(defel->defname, "storage") == 0)
+ newelm = deparse_Type_Storage(typForm);
+ else if (strcmp(defel->defname, "receive") == 0)
+ newelm = deparse_Type_Receive(typForm);
+ else if (strcmp(defel->defname, "send") == 0)
+ newelm = deparse_Type_Send(typForm);
+ else if (strcmp(defel->defname, "typmod_in") == 0)
+ newelm = deparse_Type_Typmod_In(typForm);
+ else if (strcmp(defel->defname, "typmod_out") == 0)
+ newelm = deparse_Type_Typmod_Out(typForm);
+ else if (strcmp(defel->defname, "analyze") == 0)
+ newelm = deparse_Type_Analyze(typForm);
+ else if (strcmp(defel->defname, "subscript") == 0)
+ newelm = deparse_Type_Subscript(typForm);
+ else
+ elog(ERROR, "invalid type option %s", defel->defname);
+
+ elems = lappend(elems, newelm);
+ }
+
+ ReleaseSysCache(typTup);
+
+ return new_objtree_VA("ALTER TYPE %{identity}D SET (%{definition:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(TypeRelationId,
+ objectId),
+ "definition", ObjTypeArray, elems);
+}
+
+/*
+ * Deparse a CompositeTypeStmt (CREATE TYPE AS)
+ *
+ * Given a Composite type OID and the parse tree that created it, return an
+ * ObjTree representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE TYPE %{identity}D AS (%{columns:, }s)
+ */
+static ObjTree *
+deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
+{
+ CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree;
+ HeapTuple typtup;
+ Form_pg_type typform;
+ Relation typerel;
+ List *dpcontext;
+ List *tableelts = NIL;
+
+ /* Find the pg_type entry and open the corresponding relation */
+ typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(typtup))
+ elog(ERROR, "cache lookup failed for type with OID %u", objectId);
+ typform = (Form_pg_type) GETSTRUCT(typtup);
+ typerel = relation_open(typform->typrelid, AccessShareLock);
+
+ dpcontext = deparse_context_for(RelationGetRelationName(typerel),
+ RelationGetRelid(typerel));
+
+ tableelts = deparse_TableElements(typerel, node->coldeflist, dpcontext,
+ false, /* not typed */
+ true); /* composite type */
+
+ table_close(typerel, AccessShareLock);
+ ReleaseSysCache(typtup);
+
+ return new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(TypeRelationId, objectId),
+ "columns", ObjTypeArray, tableelts);
+}
+
+/*
+ * Deparse a CreateConversionStmt
+ *
+ * Given a conversion OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE %{default}s CONVERSION %{identity}D FOR %{source}L TO %{dest}L
+ * FROM %{function}D
+ */
+static ObjTree *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+ HeapTuple conTup;
+ Relation convrel;
+ Form_pg_conversion conForm;
+ ObjTree *ret;
+
+ convrel = table_open(ConversionRelationId, AccessShareLock);
+ conTup = get_catalog_object_by_oid(convrel, Anum_pg_conversion_oid, objectId);
+ if (!HeapTupleIsValid(conTup))
+ elog(ERROR, "cache lookup failed for conversion with OID %u", objectId);
+ conForm = (Form_pg_conversion) GETSTRUCT(conTup);
+
+ ret = new_objtree_VA("CREATE %{default}s CONVERSION %{identity}D FOR %{source}L TO %{dest}L FROM %{function}D", 5,
+ "default", ObjTypeString,
+ conForm->condefault ? "DEFAULT" : "",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(conForm->connamespace,
+ NameStr(conForm->conname)),
+ "source", ObjTypeString,
+ (char *)pg_encoding_to_char(conForm->conforencoding),
+ "dest", ObjTypeString,
+ (char *)pg_encoding_to_char(conForm->contoencoding),
+ "function", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ conForm->conproc));
+
+ table_close(convrel, AccessShareLock);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateEnumStmt (CREATE TYPE AS ENUM)
+ *
+ * Given a Enum type OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE TYPE %{identity}D AS ENUM (%{values:, }L)
+ */
+static ObjTree *
+deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
+{
+ CreateEnumStmt *node = (CreateEnumStmt *) parsetree;
+ List *values = NIL;
+ ListCell *cell;
+
+ foreach(cell, node->vals)
+ values = lappend(values, new_string_object(strVal(lfirst_node(String, cell))));
+
+ return new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(TypeRelationId, objectId),
+ "values", ObjTypeArray, values);
+}
+
+/*
+ * Deparse a CreateExtensionStmt
+ *
+ * Given an extension OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE EXTENSION %{if_not_exists}s %{name}I %{options: }s
+ */
+static ObjTree *
+deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
+{
+ CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+ Relation pg_extension;
+ HeapTuple extTup;
+ Form_pg_extension extForm;
+ ObjTree *tmp;
+ List *list = NIL;
+ ListCell *cell;
+
+ pg_extension = table_open(ExtensionRelationId, AccessShareLock);
+ extTup = get_catalog_object_by_oid(pg_extension, Anum_pg_extension_oid, objectId);
+ if (!HeapTupleIsValid(extTup))
+ elog(ERROR, "cache lookup failed for extension with OID %u", objectId);
+ extForm = (Form_pg_extension) GETSTRUCT(extTup);
+
+ /* List of options */
+ foreach(cell, node->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(cell);
+
+ if (strcmp(opt->defname, "schema") == 0)
+ {
+ /* Skip this one; we add one unconditionally below */
+ continue;
+ }
+ else if (strcmp(opt->defname, "new_version") == 0)
+ {
+ tmp = new_objtree_VA("VERSION %{version}L", 2,
+ "type", ObjTypeString, "version",
+ "version", ObjTypeString, defGetString(opt));
+ list = lappend(list, new_object_object(tmp));
+ }
+ else if (strcmp(opt->defname, "old_version") == 0)
+ {
+ tmp = new_objtree_VA("FROM %{version}L", 2,
+ "type", ObjTypeString, "from",
+ "version", ObjTypeString, defGetString(opt));
+ list = lappend(list, new_object_object(tmp));
+ }
+ else
+ elog(ERROR, "unsupported option %s", opt->defname);
+ }
+
+ /* Add the SCHEMA option */
+ tmp = new_objtree_VA("SCHEMA %{schema}I", 2,
+ "type", ObjTypeString, "schema",
+ "schema", ObjTypeString,
+ get_namespace_name(extForm->extnamespace));
+ list = lappend(list, new_object_object(tmp));
+ table_close(pg_extension, AccessShareLock);
+
+ return new_objtree_VA("CREATE EXTENSION %{if_not_exists}s %{name}I %{options: }s", 3,
+ "if_not_exists", ObjTypeString,
+ node->if_not_exists ? "IF NOT EXISTS" : "",
+ "name", ObjTypeString, node->extname,
+ "options", ObjTypeArray, list);
+}
+
+/*
+ * If a column name is specified, add an element for it; otherwise it's a
+ * table-level option.
+ */
+static ObjTree *
+deparse_FdwOptions(List *options, char *colname, bool altercoloptions)
+{
+ ObjTree *ret;
+
+ if (colname)
+ ret = new_objtree_VA("ALTER COLUMN %{column}I %{options}s", 2,
+ "column", ObjTypeString, colname,
+ "options", ObjTypeString,
+ altercoloptions ? "OPTIONS" : "");
+ else
+ ret = new_objtree("OPTIONS");
+
+ if (options != NIL)
+ {
+ List *optout = NIL;
+ ListCell *cell;
+
+ foreach(cell, options)
+ {
+ DefElem *elem;
+ ObjTree *opt;
+
+ elem = (DefElem *) lfirst(cell);
+
+ switch (elem->defaction)
+ {
+ case DEFELEM_UNSPEC:
+ opt = new_objtree_VA("%{label}I %{value}L", 2,
+ "label", ObjTypeString, elem->defname,
+ "value", ObjTypeString,
+ elem->arg ? defGetString(elem) :
+ defGetBoolean(elem) ? "TRUE" : "FALSE");
+ break;
+ case DEFELEM_SET:
+ opt = new_objtree_VA("SET %{label}I %{value}L", 2,
+ "label", ObjTypeString, elem->defname,
+ "value", ObjTypeString,
+ elem->arg ? defGetString(elem) :
+ defGetBoolean(elem) ? "TRUE" : "FALSE");
+ break;
+ case DEFELEM_ADD:
+ opt = new_objtree_VA("ADD %{label}I %{value}L", 2,
+ "label", ObjTypeString, elem->defname,
+ "value", ObjTypeString,
+ elem->arg ? defGetString(elem) :
+ defGetBoolean(elem) ? "TRUE" : "FALSE");
+ break;
+ case DEFELEM_DROP:
+ opt = new_objtree_VA("DROP %{label}I", 1,
+ "label", ObjTypeString, elem->defname);
+ break;
+ default:
+ elog(ERROR, "invalid def action %d", elem->defaction);
+ opt = NULL;
+ }
+
+ optout = lappend(optout, new_object_object(opt));
+ }
+
+ append_array_object(ret, "(%{option: ,}s)", optout);
+ }
+ else
+ append_not_present(ret);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateFdwStmt (CREATE FOREIGN DATA WRAPPER)
+ *
+ * Given an FDW OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE FOREIGN DATA WRAPPER %{identity}I %{handler}s %{validator}s %{generic_options}s
+ */
+static ObjTree *
+deparse_CreateFdwStmt(Oid objectId, Node *parsetree)
+{
+ CreateFdwStmt *node = (CreateFdwStmt *) parsetree;
+ HeapTuple fdwTup;
+ Form_pg_foreign_data_wrapper fdwForm;
+ Relation rel;
+ ObjTree *ret;
+ ObjTree *tmp;
+
+ rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+ fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+ ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(fdwTup))
+ elog(ERROR, "cache lookup failed for foreign-data wrapper with OID %u",
+ objectId);
+
+ fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+ ret = new_objtree_VA("CREATE FOREIGN DATA WRAPPER %{identity}I", 1,
+ "identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+ /* Add HANDLER clause */
+ if (!OidIsValid(fdwForm->fdwhandler))
+ tmp = new_objtree("NO HANDLER");
+ else
+ {
+ tmp = new_objtree_VA("HANDLER %{procedure}D", 1,
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ fdwForm->fdwhandler));
+ }
+ append_object_object(ret, "%{handler}s", tmp);
+
+ /* Add VALIDATOR clause */
+ if (!OidIsValid(fdwForm->fdwvalidator))
+ tmp = new_objtree("NO VALIDATOR");
+ else
+ {
+ tmp = new_objtree_VA("VALIDATOR %{procedure}D", 1,
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ fdwForm->fdwvalidator));
+ }
+ append_object_object(ret, "%{validator}s", tmp);
+
+ /* Add an OPTIONS clause, if any */
+ append_object_object(ret, "%{generic_options}s",
+ deparse_FdwOptions(node->options, NULL, false));
+
+ ReleaseSysCache(fdwTup);
+ table_close(rel, RowExclusiveLock);
+
+ return ret;
+}
+
+/*
+ * Deparse an AlterFdwStmt (ALTER FOREIGN DATA WRAPPER)
+ *
+ * Given an FDW OID and the parse tree that created it, return an ObjTree
+ * representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER FOREIGN DATA WRAPPER %{identity}I %{fdw_options: }s %{generic_options}D
+ */
+static ObjTree *
+deparse_AlterFdwStmt(Oid objectId, Node *parsetree)
+{
+ AlterFdwStmt *node = (AlterFdwStmt *) parsetree;
+ HeapTuple fdwTup;
+ Form_pg_foreign_data_wrapper fdwForm;
+ Relation rel;
+ ObjTree *ret;
+ List *fdw_options = NIL;
+ ListCell *cell;
+
+ rel = table_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+ fdwTup = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+ ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(fdwTup))
+ elog(ERROR, "cache lookup failed for foreign-data wrapper with OID %u",
+ objectId);
+
+ fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(fdwTup);
+
+ ret = new_objtree_VA("ALTER FOREIGN DATA WRAPPER %{identity}I", 1,
+ "identity", ObjTypeString, NameStr(fdwForm->fdwname));
+
+ /*
+ * Iterate through options, to see what changed, but use catalog as a
+ * basis for new values.
+ */
+ foreach(cell, node->func_options)
+ {
+ DefElem *elem;
+ ObjTree *tmp;
+
+ elem = lfirst(cell);
+
+ if (pg_strcasecmp(elem->defname, "handler") == 0)
+ {
+ /* add HANDLER clause */
+ if (!OidIsValid(fdwForm->fdwhandler))
+ tmp = new_objtree("NO HANDLER");
+ else
+ {
+ tmp = new_objtree_VA("HANDLER %{procedure}D", 1,
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ fdwForm->fdwhandler));
+ }
+ fdw_options = lappend(fdw_options, new_object_object(tmp));
+ }
+ else if (pg_strcasecmp(elem->defname, "validator") == 0)
+ {
+ /* add VALIDATOR clause */
+ if (!OidIsValid(fdwForm->fdwvalidator))
+ tmp = new_objtree("NO VALIDATOR");
+ else
+ {
+ tmp = new_objtree_VA("VALIDATOR %{procedure}D", 1,
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ fdwForm->fdwvalidator));
+ }
+ fdw_options = lappend(fdw_options, new_object_object(tmp));
+ }
+ }
+
+ /* Add HANDLER/VALIDATOR if specified */
+ append_array_object(ret, "%{fdw_options: }s", fdw_options);
+
+ /* Add an OPTIONS clause, if any */
+ append_object_object(ret, "%{generic_options}D",
+ deparse_FdwOptions(node->options, NULL, false));
+
+ ReleaseSysCache(fdwTup);
+ table_close(rel, RowExclusiveLock);
+
+ return ret;
+}
+
+/*
+ * Deparse a CREATE TYPE AS RANGE statement
+ *
+ * Given a Range type OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE TYPE %{identity}D AS RANGE (%{definition:, }s)
+ */
+static ObjTree *
+deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
+{
+ ObjTree *tmp;
+ List *definition = NIL;
+ Relation pg_range;
+ HeapTuple rangeTup;
+ Form_pg_range rangeForm;
+ ScanKeyData key[1];
+ SysScanDesc scan;
+
+ pg_range = table_open(RangeRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_range_rngtypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objectId));
+
+ scan = systable_beginscan(pg_range, RangeTypidIndexId, true,
+ NULL, 1, key);
+
+ rangeTup = systable_getnext(scan);
+ if (!HeapTupleIsValid(rangeTup))
+ elog(ERROR, "cache lookup failed for range with type OID %u",
+ objectId);
+
+ rangeForm = (Form_pg_range) GETSTRUCT(rangeTup);
+
+ /* SUBTYPE */
+ tmp = new_objtree_VA("SUBTYPE = %{type}D", 2,
+ "clause", ObjTypeString, "subtype",
+ "type", ObjTypeObject,
+ new_objtree_for_qualname_id(TypeRelationId, rangeForm->rngsubtype));
+ definition = lappend(definition, new_object_object(tmp));
+
+ /* SUBTYPE_OPCLASS */
+ if (OidIsValid(rangeForm->rngsubopc))
+ {
+ tmp = new_objtree_VA("SUBTYPE_OPCLASS = %{opclass}D", 2,
+ "clause", ObjTypeString, "opclass",
+ "opclass", ObjTypeObject,
+ new_objtree_for_qualname_id(OperatorClassRelationId,
+ rangeForm->rngsubopc));
+ definition = lappend(definition, new_object_object(tmp));
+ }
+
+ /* COLLATION */
+ if (OidIsValid(rangeForm->rngcollation))
+ {
+ tmp = new_objtree_VA("COLLATION = %{collation}D", 2,
+ "clause", ObjTypeString, "collation",
+ "collation", ObjTypeObject,
+ new_objtree_for_qualname_id(CollationRelationId,
+ rangeForm->rngcollation));
+ definition = lappend(definition, new_object_object(tmp));
+ }
+
+ /* CANONICAL */
+ if (OidIsValid(rangeForm->rngcanonical))
+ {
+ tmp = new_objtree_VA("CANONICAL = %{canonical}D", 2,
+ "clause", ObjTypeString, "canonical",
+ "canonical", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ rangeForm->rngcanonical));
+ definition = lappend(definition, new_object_object(tmp));
+ }
+
+ /* SUBTYPE_DIFF */
+ if (OidIsValid(rangeForm->rngsubdiff))
+ {
+ tmp = new_objtree_VA("SUBTYPE_DIFF = %{subtype_diff}D", 2,
+ "clause", ObjTypeString, "subtype_diff",
+ "subtype_diff", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ rangeForm->rngsubdiff));
+ definition = lappend(definition, new_object_object(tmp));
+ }
+
+ systable_endscan(scan);
+ table_close(pg_range, RowExclusiveLock);
+
+ return new_objtree_VA("CREATE TYPE %{identity}D AS RANGE (%{definition:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(TypeRelationId, objectId),
+ "definition", ObjTypeArray, definition);
+}
+
+/*
+ * Deparse an AlterEnumStmt.
+ *
+ * Given an enum OID and a parse tree that modified it, return an ObjTree
+ * representing the alter type.
+ *
+ * Verbose syntax
+ * ALTER TYPE %{identity}D ADD VALUE %{if_not_exists}s %{value}L
+ * %{after_or_before}s %{neighbor}L
+ * OR
+ * ALTER TYPE %{identity}D RENAME VALUE %{value}L TO %{newvalue}L
+ */
+static ObjTree *
+deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
+{
+ AlterEnumStmt *node = (AlterEnumStmt *) parsetree;
+ ObjTree *ret;
+
+ ret = new_objtree_VA("ALTER TYPE %{identity}D", 1,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(TypeRelationId,
+ objectId));
+
+ if (!node->oldVal)
+ {
+ append_format_string(ret, "ADD VALUE");
+ append_string_object(ret, "%{if_not_exists}s", "if_not_exists",
+ node->skipIfNewValExists ? "IF NOT EXISTS" : "");
+
+ append_string_object(ret, "%{value}L", "value", node->newVal);
+
+ if (node->newValNeighbor)
+ {
+ append_string_object(ret, "%{after_or_before}s",
+ "after_or_before",
+ node->newValIsAfter ? "AFTER" : "BEFORE");
+ append_string_object(ret, "%{neighbor}L", "neighbor",
+ node->newValNeighbor);
+ }
+ }
+ else
+ {
+ append_string_object(ret, "RENAME VALUE %{value}L TO", "value",
+ node->oldVal);
+ append_string_object(ret, "%{newvalue}L", "newvalue", node->newVal);
+ }
+
+ return ret;
+}
+
+/*
+ * Deparse an AlterExtensionStmt (ALTER EXTENSION .. UPDATE TO VERSION)
+ *
+ * Given an extension OID and a parse tree that modified it, return an ObjTree
+ * representing the alter type.
+ *
+ * Verbose syntax
+ * ALTER EXTENSION %{identity}I UPDATE %{options: }s
+ */
+static ObjTree *
+deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
+{
+ AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
+ Relation pg_extension;
+ HeapTuple extTup;
+ Form_pg_extension extForm;
+ ObjTree *ret;
+ List *list = NIL;
+ ListCell *cell;
+ DefElem *d_new_version = NULL;
+
+ pg_extension = table_open(ExtensionRelationId, AccessShareLock);
+ extTup = get_catalog_object_by_oid(pg_extension, Anum_pg_extension_oid, objectId);
+ if (!HeapTupleIsValid(extTup))
+ elog(ERROR, "cache lookup failed for extension with OID %u",
+ objectId);
+ extForm = (Form_pg_extension) GETSTRUCT(extTup);
+
+ foreach(cell, node->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(cell);
+
+ if (strcmp(opt->defname, "new_version") == 0)
+ {
+ ObjTree *tmp;
+
+ if (d_new_version)
+ elog(ERROR, "conflicting or redundant options for extension with OID %u",
+ objectId);
+
+ d_new_version = opt;
+
+ tmp = new_objtree_VA("TO %{version}L", 2,
+ "type", ObjTypeString, "version",
+ "version", ObjTypeString, defGetString(opt));
+ list = lappend(list, new_object_object(tmp));
+ }
+ else
+ elog(ERROR, "unsupported option %s", opt->defname);
+ }
+
+ ret = new_objtree_VA("ALTER EXTENSION %{identity}I UPDATE %{options: }s", 2,
+ "identity", ObjTypeString, NameStr(extForm->extname),
+ "options", ObjTypeArray, list);
+
+ table_close(pg_extension, AccessShareLock);
+
+ return ret;
+}
+
+/*
+ * Deparse an AlterExtensionContentsStmt (ALTER EXTENSION ext ADD/DROP object)
+ *
+ * Verbose syntax
+ * ALTER EXTENSION %{extidentity}I %{extoption}s %{extobjtype}s
+ * ADD/DROP %{objidentity}s
+ */
+static ObjTree *
+deparse_AlterExtensionContentsStmt(Oid objectId, Node *parsetree,
+ ObjectAddress objectAddress)
+{
+ AlterExtensionContentsStmt *node = (AlterExtensionContentsStmt *) parsetree;
+
+ Assert(node->action == +1 || node->action == -1);
+
+ return new_objtree_VA("ALTER EXTENSION %{extidentity}I %{extoption}s %{extobjtype}s %{objidentity}s", 4,
+ "extidentity", ObjTypeString, node->extname,
+ "extoption", ObjTypeString,
+ node->action == +1 ? "ADD" : "DROP",
+ "extobjtype", ObjTypeString,
+ stringify_objtype(node->objtype, false),
+ "objidentity", ObjTypeString,
+ getObjectIdentity(&objectAddress, false));
+}
+
+/*
+ * Deparse an CreateCastStmt.
+ *
+ * Given a sequence OID and a parse tree that modified it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE CAST (%{sourcetype}T AS %{targettype}T) %{mechanism}s %{context}s
+ */
+static ObjTree *
+deparse_CreateCastStmt(Oid objectId, Node *parsetree)
+{
+ CreateCastStmt *node = (CreateCastStmt *) parsetree;
+ Relation castrel;
+ HeapTuple castTup;
+ Form_pg_cast castForm;
+ ObjTree *ret;
+ char *context;
+
+ castrel = table_open(CastRelationId, AccessShareLock);
+ castTup = get_catalog_object_by_oid(castrel, Anum_pg_cast_oid, objectId);
+ if (!HeapTupleIsValid(castTup))
+ elog(ERROR, "cache lookup failed for cast with OID %u", objectId);
+ castForm = (Form_pg_cast) GETSTRUCT(castTup);
+
+ ret = new_objtree_VA("CREATE CAST (%{sourcetype}T AS %{targettype}T)", 2,
+ "sourcetype", ObjTypeObject,
+ new_objtree_for_type(castForm->castsource, -1),
+ "targettype", ObjTypeObject,
+ new_objtree_for_type(castForm->casttarget, -1));
+
+ if (node->inout)
+ append_string_object(ret, "%{mechanism}s", "mechanism",
+ "WITH INOUT");
+ else if (node->func == NULL)
+ append_string_object(ret, "%{mechanism}s", "mechanism",
+ "WITHOUT FUNCTION");
+ else
+ {
+ ObjTree *tmp_obj;
+ StringInfoData func;
+ HeapTuple funcTup;
+ Form_pg_proc funcForm;
+ int i;
+
+ funcTup = SearchSysCache1(PROCOID, castForm->castfunc);
+ funcForm = (Form_pg_proc) GETSTRUCT(funcTup);
+
+ initStringInfo(&func);
+ appendStringInfo(&func, "%s(",
+ quote_qualified_identifier(get_namespace_name(funcForm->pronamespace),
+ NameStr(funcForm->proname)));
+ for (i = 0; i < funcForm->pronargs; i++)
+ {
+ if (i != 0)
+ appendStringInfoChar(&func, ',');
+
+ appendStringInfoString(&func,
+ format_type_be_qualified(funcForm->proargtypes.values[i]));
+ }
+ appendStringInfoChar(&func, ')');
+
+ tmp_obj = new_objtree_VA("WITH FUNCTION %{castfunction}s", 1,
+ "castfunction", ObjTypeString, func.data);
+ append_object_object(ret, "%{mechanism}s", tmp_obj);
+
+ ReleaseSysCache(funcTup);
+ }
+
+ switch (node->context)
+ {
+ case COERCION_IMPLICIT:
+ context = "AS IMPLICIT";
+ break;
+ case COERCION_ASSIGNMENT:
+ context = "AS ASSIGNMENT";
+ break;
+ case COERCION_EXPLICIT:
+ context = "";
+ break;
+ default:
+ elog(ERROR, "invalid coercion code %c", node->context);
+ return NULL; /* keep compiler quiet */
+ }
+ append_string_object(ret, "%{context}s", "context", context);
+
+ table_close(castrel, AccessShareLock);
+
+ return ret;
+}
+
+/*
+ * Deparse an ALTER DEFAULT PRIVILEGES statement.
+ *
+ * Verbose syntax
+ * ALTER DEFAULT PRIVILEGES %{in_schema}s %{for_roles}s %{grant}s
+ */
+static ObjTree *
+deparse_AlterDefaultPrivilegesStmt(CollectedCommand *cmd)
+{
+ ObjTree *ret;
+ AlterDefaultPrivilegesStmt *stmt = (AlterDefaultPrivilegesStmt *) cmd->parsetree;
+ List *roles = NIL;
+ List *schemas = NIL;
+ List *grantees;
+ List *privs;
+ ListCell *cell;
+ ObjTree *tmp;
+ ObjTree *grant;
+ char *objtype;
+
+ ret = new_objtree("ALTER DEFAULT PRIVILEGES");
+
+ /* Scan the parse node to dig out the FOR ROLE and IN SCHEMA clauses */
+ foreach(cell, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(cell);
+ ListCell *cell2;
+
+ Assert(IsA(opt, DefElem));
+ Assert(IsA(opt->arg, List));
+ if (strcmp(opt->defname, "roles") == 0)
+ {
+ foreach(cell2, (List *) opt->arg)
+ {
+ RoleSpec *rolespec = lfirst(cell2);
+ ObjTree *obj = new_objtree_for_rolespec(rolespec);
+
+ roles = lappend(roles, new_object_object(obj));
+ }
+ }
+ else if (strcmp(opt->defname, "schemas") == 0)
+ {
+ foreach(cell2, (List *) opt->arg)
+ {
+ String *val = lfirst_node(String, cell2);
+
+ schemas = lappend(schemas, new_string_object(strVal(val)));
+ }
+ }
+ }
+
+ /* Add the IN SCHEMA clause, if any */
+ tmp = new_objtree("IN SCHEMA");
+ append_array_object(tmp, "%{schemas:, }I", schemas);
+ if (schemas == NIL)
+ append_not_present(tmp);
+ append_object_object(ret, "%{in_schema}s", tmp);
+
+ /* Add the FOR ROLE clause, if any */
+ tmp = new_objtree("FOR ROLE");
+ append_array_object(tmp, "%{roles:, }R", roles);
+ if (roles == NIL)
+ append_not_present(tmp);
+ append_object_object(ret, "%{for_roles}s", tmp);
+
+ /*
+ * Add the GRANT subcommand
+ * Verbose syntax
+ * GRANT %{privileges:, }s ON %{target}s TO %{grantees:, }R %{grant_option}s
+ * or
+ * REVOKE %{grant_option}s %{privileges:, }s ON %{target}s FROM %{grantees:, }R
+ */
+ if (stmt->action->is_grant)
+ grant = new_objtree("GRANT");
+ else
+ {
+ grant = new_objtree("REVOKE");
+
+ /* Add the GRANT OPTION clause for REVOKE subcommand */
+ tmp = new_objtree_VA("GRANT OPTION FOR", 1,
+ "present", ObjTypeBool,
+ stmt->action->grant_option);
+ append_object_object(grant, "%{grant_option}s", tmp);
+ }
+
+ /*
+ * Add the privileges list. This uses the parser struct, as opposed to
+ * the InternalGrant format used by GRANT. There are enough other
+ * differences that this doesn't seem worth improving.
+ */
+ if (stmt->action->privileges == NIL)
+ privs = list_make1(new_string_object("ALL PRIVILEGES"));
+ else
+ {
+ privs = NIL;
+
+ foreach(cell, stmt->action->privileges)
+ {
+ AccessPriv *priv = lfirst(cell);
+
+ Assert(priv->cols == NIL);
+ privs = lappend(privs, new_string_object(priv->priv_name));
+ }
+ }
+
+ append_array_object(grant, "%{privileges:, }s", privs);
+
+ switch (cmd->d.defprivs.objtype)
+ {
+ case OBJECT_TABLE:
+ objtype = "TABLES";
+ break;
+ case OBJECT_FUNCTION:
+ objtype = "FUNCTIONS";
+ break;
+ case OBJECT_ROUTINE:
+ objtype = "ROUTINES";
+ break;
+ case OBJECT_SEQUENCE:
+ objtype = "SEQUENCES";
+ break;
+ case OBJECT_TYPE:
+ objtype = "TYPES";
+ break;
+ case OBJECT_SCHEMA:
+ objtype = "SCHEMAS";
+ break;
+ default:
+ elog(ERROR, "invalid OBJECT value %d for default privileges", cmd->d.defprivs.objtype);
+ }
+
+ /* Add the target object type */
+ append_string_object(grant, "ON %{target}s", "target", objtype);
+
+ /* Add the grantee list */
+ grantees = NIL;
+ foreach(cell, stmt->action->grantees)
+ {
+ RoleSpec *spec = (RoleSpec *) lfirst(cell);
+ ObjTree *obj = new_objtree_for_rolespec(spec);
+
+ grantees = lappend(grantees, new_object_object(obj));
+ }
+
+ if (stmt->action->is_grant)
+ {
+ append_array_object(grant, "TO %{grantees:, }R", grantees);
+
+ /* Add the WITH GRANT OPTION clause for GRANT subcommand */
+ tmp = new_objtree_VA("WITH GRANT OPTION", 1,
+ "present", ObjTypeBool,
+ stmt->action->grant_option);
+ append_object_object(grant, "%{grant_option}s", tmp);
+ }
+ else
+ append_array_object(grant, "FROM %{grantees:, }R", grantees);
+
+ append_object_object(ret, "%{grant}s", grant);
+
+ return ret;
+}
+
+/*
+ * Deparse the CREATE DOMAIN
+ *
+ * Given a function OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE DOMAIN %{identity}D AS %{type}T %{not_null}s %{constraints}s
+ * %{collation}s
+ */
+static ObjTree *
+deparse_CreateDomain(Oid objectId, Node *parsetree)
+{
+ ObjTree *ret;
+ ObjTree *tmp_obj;
+ HeapTuple typTup;
+ Form_pg_type typForm;
+ List *constraints;
+ char *defval;
+
+ typTup = SearchSysCache1(TYPEOID, objectId);
+ if (!HeapTupleIsValid(typTup))
+ elog(ERROR, "cache lookup failed for domain with OID %u", objectId);
+ typForm = (Form_pg_type) GETSTRUCT(typTup);
+
+ ret = new_objtree_VA("CREATE DOMAIN %{identity}D AS %{type}T %{not_null}s", 3,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(TypeRelationId, objectId),
+ "type", ObjTypeObject,
+ new_objtree_for_type(typForm->typbasetype,
+ typForm->typtypmod),
+ "not_null", ObjTypeString,
+ typForm->typnotnull ? "NOT NULL" : "");
+
+ constraints = obtainConstraints(NIL, InvalidOid, objectId,
+ ConstrObjDomain);
+ if (constraints == NIL)
+ {
+ tmp_obj = new_objtree("");
+ append_not_present(tmp_obj);
+ }
+ else
+ tmp_obj = new_objtree_VA("%{elements:, }s", 1,
+ "elements", ObjTypeArray, constraints);
+ append_object_object(ret, "%{constraints}s", tmp_obj);
+
+ tmp_obj = new_objtree("COLLATE");
+ if (OidIsValid(typForm->typcollation))
+ {
+ ObjTree *collname;
+
+ collname = new_objtree_for_qualname_id(CollationRelationId,
+ typForm->typcollation);
+ append_object_object(tmp_obj, "%{name}D", collname);
+ }
+ else
+ append_not_present(tmp_obj);
+ append_object_object(ret, "%{collation}s", tmp_obj);
+
+ defval = DomainGetDefault(typTup, true);
+ if (defval)
+ append_string_object(ret, "DEFAULT %{default}s", "default", defval);
+
+ ReleaseSysCache(typTup);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateFunctionStmt (CREATE FUNCTION)
+ *
+ * Given a function OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ *
+ * CREATE %{or_replace}s FUNCTION %{signature}s RETURNS %{return_type}s
+ * LANGUAGE %{transform_type}s %{language}I %{window}s %{volatility}s
+ * %{parallel_safety}s %{leakproof}s %{strict}s %{security_definer}s
+ * %{cost}s %{rows}s %{support}s %{set_options: }s AS %{objfile}L,
+ * %{symbol}L
+ */
+static ObjTree *
+deparse_CreateFunction(Oid objectId, Node *parsetree)
+{
+ CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree;
+ ObjTree *ret;
+ ObjTree *tmp_obj;
+ Datum tmpdatum;
+ char *source;
+ char *probin = NULL;
+ List *params;
+ List *defaults;
+ List *sets = NIL;
+ List *types = NIL;
+ ListCell *cell;
+ ListCell *curdef;
+ ListCell *table_params = NULL;
+ HeapTuple procTup;
+ Form_pg_proc procForm;
+ HeapTuple langTup;
+ Oid *typarray;
+ Oid *trftypes;
+ Form_pg_language langForm;
+ int i;
+ int typnum;
+ int ntypes;
+ int paramcount = list_length(node->parameters);
+ bool isnull;
+ bool isfunction;
+
+ /* Get the pg_proc tuple */
+ procTup = SearchSysCache1(PROCOID, objectId);
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function with OID %u",
+ objectId);
+ procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /* Get the corresponding pg_language tuple */
+ langTup = SearchSysCache1(LANGOID, procForm->prolang);
+ if (!HeapTupleIsValid(langTup))
+ elog(ERROR, "cache lookup failed for language with OID %u",
+ procForm->prolang);
+ langForm = (Form_pg_language) GETSTRUCT(langTup);
+
+ /*
+ * Determine useful values for prosrc and probin. We cope with probin
+ * being either NULL or "-", but prosrc must have a valid value.
+ */
+ tmpdatum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc in function with OID %u", objectId);
+ source = TextDatumGetCString(tmpdatum);
+
+ /* Determine a useful value for probin */
+ tmpdatum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_probin, &isnull);
+ if (!isnull)
+ {
+ probin = TextDatumGetCString(tmpdatum);
+ if (probin[0] == '\0' || strcmp(probin, "-") == 0)
+ probin = NULL;
+ }
+
+ ret = new_objtree_VA("CREATE %{or_replace}s", 1,
+ "or_replace", ObjTypeString,
+ node->replace ? "OR REPLACE" : "");
+
+ /*
+ * To construct the arguments array, extract the type OIDs from the
+ * function's pg_proc entry. If pronargs equals the parameter list
+ * length, there are no OUT parameters and thus we can extract the type
+ * OID from proargtypes; otherwise we need to decode proallargtypes, which
+ * is a bit more involved.
+ */
+ typarray = palloc(paramcount * sizeof(Oid));
+ if (paramcount > procForm->pronargs)
+ {
+ Datum alltypes;
+ Datum *values;
+ bool *nulls;
+ int nelems;
+
+ alltypes = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_proallargtypes, &isnull);
+ if (isnull)
+ elog(ERROR, "null proallargtypes, more number of parameters than args in function with OID %u",
+ objectId);
+ deconstruct_array(DatumGetArrayTypeP(alltypes),
+ OIDOID, 4, 't', 'i',
+ &values, &nulls, &nelems);
+ if (nelems != paramcount)
+ elog(ERROR, "mismatched proallargatypes");
+ for (i = 0; i < paramcount; i++)
+ typarray[i] = values[i];
+ }
+ else
+ {
+ for (i = 0; i < paramcount; i++)
+ typarray[i] = procForm->proargtypes.values[i];
+ }
+
+ /*
+ * If there are any default expressions, we read the deparsed expression
+ * as a list so that we can attach them to each argument.
+ */
+ tmpdatum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_proargdefaults, &isnull);
+ if (!isnull)
+ {
+ defaults = FunctionGetDefaults(DatumGetTextP(tmpdatum));
+ curdef = list_head(defaults);
+ }
+ else
+ {
+ defaults = NIL;
+ curdef = NULL;
+ }
+
+ /*
+ * Now iterate over each parameter in the parse tree to create the
+ * parameters array.
+ */
+ params = NIL;
+ typnum = 0;
+ foreach(cell, node->parameters)
+ {
+ FunctionParameter *param = (FunctionParameter *) lfirst(cell);
+ ObjTree *param_obj;
+ ObjTree *defaultval;
+ ObjTree *name;
+
+ /*
+ * A PARAM_TABLE parameter indicates the end of input arguments; the
+ * following parameters are part of the return type. We ignore them
+ * here, but keep track of the current position in the list so that we
+ * can easily produce the return type below.
+ */
+ if (param->mode == FUNC_PARAM_TABLE)
+ {
+ table_params = cell;
+ break;
+ }
+
+ /*
+ * Verbose syntax for paramater: %{mode}s %{name}s %{type}T
+ * %{default}s
+ */
+ param_obj = new_objtree_VA("%{mode}s", 1,
+ "mode", ObjTypeString,
+ param->mode == FUNC_PARAM_OUT ? "OUT" :
+ param->mode == FUNC_PARAM_INOUT ? "INOUT" :
+ param->mode == FUNC_PARAM_VARIADIC ? "VARIADIC" :
+ "IN");
+
+ /* Optional wholesale suppression of "name" occurs here */
+ name = new_objtree("");
+ if (param->name)
+ append_string_object(name, "%{name}I", "name", param->name);
+ else
+ {
+ append_null_object(name, "%{name}I");
+ append_not_present(name);
+ }
+
+ append_object_object(param_obj, "%{name}s", name);
+
+ defaultval = new_objtree("DEFAULT");
+ if (PointerIsValid(param->defexpr))
+ {
+ char *expr;
+
+ if (curdef == NULL)
+ elog(ERROR, "proargdefaults list too short");
+ expr = lfirst(curdef);
+
+ append_string_object(defaultval, "%{value}s", "value", expr);
+ curdef = lnext(defaults, curdef);
+ }
+ else
+ append_not_present(defaultval);
+
+ append_object_object(param_obj, "%{type}T",
+ new_objtree_for_type(typarray[typnum++], -1));
+
+ append_object_object(param_obj, "%{default}s", defaultval);
+
+ params = lappend(params, new_object_object(param_obj));
+ }
+
+ tmp_obj = new_objtree_VA("%{identity}D", 1,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ objectId));
+
+ append_format_string(tmp_obj, "(");
+ append_array_object(tmp_obj, "%{arguments:, }s", params);
+ append_format_string(tmp_obj, ")");
+
+ isfunction = (procForm->prokind != PROKIND_PROCEDURE);
+
+ if (isfunction)
+ append_object_object(ret, "FUNCTION %{signature}s", tmp_obj);
+ else
+ append_object_object(ret, "PROCEDURE %{signature}s", tmp_obj);
+
+ /*
+ * A return type can adopt one of two forms: either a [SETOF] some_type,
+ * or a TABLE(list-of-types). We can tell the second form because we saw
+ * a table param above while scanning the argument list.
+ */
+ if (table_params == NULL)
+ {
+ tmp_obj = new_objtree_VA("", 1,
+ "return_form", ObjTypeString, "plain");
+ append_string_object(tmp_obj, "%{setof}s", "setof",
+ procForm->proretset ? "SETOF" : "");
+ append_object_object(tmp_obj, "%{rettype}T",
+ new_objtree_for_type(procForm->prorettype, -1));
+ }
+ else
+ {
+ List *rettypes = NIL;
+ ObjTree *param_obj;
+
+ tmp_obj = new_objtree_VA("TABLE", 1,
+ "return_form", ObjTypeString, "table");
+ for (; table_params != NULL; table_params = lnext(node->parameters, table_params))
+ {
+ FunctionParameter *param = lfirst(table_params);
+
+ param_obj = new_objtree_VA("%{name}I %{type}T", 2,
+ "name", ObjTypeString, param->name,
+ "type", ObjTypeObject,
+ new_objtree_for_type(typarray[typnum++], -1));
+ rettypes = lappend(rettypes, new_object_object(param_obj));
+ }
+
+ append_array_object(tmp_obj, "(%{rettypes:, }s)", rettypes);
+ }
+
+ if (isfunction)
+ append_object_object(ret, "RETURNS %{return_type}s", tmp_obj);
+
+ /* TRANSFORM FOR TYPE */
+ tmp_obj = new_objtree("TRANSFORM");
+
+ ntypes = get_func_trftypes(procTup, &trftypes);
+ for (i = 0; i < ntypes; i++)
+ {
+ tmp_obj = new_objtree_VA("FOR TYPE %{type}T", 1,
+ "type", ObjTypeObject,
+ new_objtree_for_type(trftypes[i], -1));
+ types = lappend(types, tmp_obj);
+ }
+
+ if (types)
+ append_array_object(tmp_obj, "%{for_type:, }s", types);
+ else
+ append_not_present(tmp_obj);
+
+ append_object_object(ret, "%{transform_type}s", tmp_obj);
+
+ append_string_object(ret, "LANGUAGE %{language}I", "language",
+ NameStr(langForm->lanname));
+
+ if (isfunction)
+ {
+ append_string_object(ret, "%{window}s", "window",
+ procForm->prokind == PROKIND_WINDOW ? "WINDOW" : "");
+ append_string_object(ret, "%{volatility}s", "volatility",
+ procForm->provolatile == PROVOLATILE_VOLATILE ?
+ "VOLATILE" :
+ procForm->provolatile == PROVOLATILE_STABLE ?
+ "STABLE" : "IMMUTABLE");
+
+ append_string_object(ret, "%{parallel_safety}s",
+ "parallel_safety",
+ procForm->proparallel == PROPARALLEL_SAFE ?
+ "PARALLEL SAFE" :
+ procForm->proparallel == PROPARALLEL_RESTRICTED ?
+ "PARALLEL RESTRICTED" : "PARALLEL UNSAFE");
+
+ append_string_object(ret, "%{leakproof}s", "leakproof",
+ procForm->proleakproof ? "LEAKPROOF" : "");
+ append_string_object(ret, "%{strict}s", "strict",
+ procForm->proisstrict ?
+ "RETURNS NULL ON NULL INPUT" :
+ "CALLED ON NULL INPUT");
+
+ append_string_object(ret, "%{security_definer}s",
+ "security_definer",
+ procForm->prosecdef ?
+ "SECURITY DEFINER" : "SECURITY INVOKER");
+
+ append_object_object(ret, "%{cost}s",
+ new_objtree_VA("COST %{cost}n", 1,
+ "cost", ObjTypeFloat,
+ procForm->procost));
+
+ tmp_obj = new_objtree("ROWS");
+ if (procForm->prorows == 0)
+ append_not_present(tmp_obj);
+ else
+ append_float_object(tmp_obj, "%{rows}n", procForm->prorows);
+ append_object_object(ret, "%{rows}s", tmp_obj);
+
+ tmp_obj = new_objtree("SUPPORT %{name}s");
+ if (procForm->prosupport)
+ {
+ Oid argtypes[] = { INTERNALOID };
+
+ /*
+ * We should qualify the support function's name if it wouldn't be
+ * resolved by lookup in the current search path.
+ */
+ append_string_object(tmp_obj, "%{name}s", "name",
+ generate_function_name(procForm->prosupport, 1,
+ NIL, argtypes,
+ false, NULL,
+ EXPR_KIND_NONE));
+ }
+ else
+ append_not_present(tmp_obj);
+
+ append_object_object(ret, "%{support}s", tmp_obj);
+ }
+
+ foreach(cell, node->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(cell);
+
+ if (strcmp(defel->defname, "set") == 0)
+ {
+ VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
+ char *value = ExtractSetVariableArgs(sstmt);
+
+ tmp_obj = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
+ sets = lappend(sets, new_object_object(tmp_obj));
+ }
+ }
+ append_array_object(ret, "%{set_options: }s", sets);
+
+ /* Add the function definition */
+ (void) SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosqlbody, &isnull);
+ if (procForm->prolang == SQLlanguageId && !isnull)
+ {
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ print_function_sqlbody(&buf, procTup);
+
+ append_string_object(ret, "%{definition}s", "definition",
+ buf.data);
+ }
+ else if (probin == NULL)
+ append_string_object(ret, "AS %{definition}L", "definition", source);
+ else
+ {
+ append_string_object(ret, "AS %{objfile}L", "objfile", probin);
+ append_string_object(ret, ", %{symbol}L", "symbol", source);
+ }
+
+ ReleaseSysCache(langTup);
+ ReleaseSysCache(procTup);
+
+ return ret;
+}
+
+/*
+ * Deparse a CREATE OPERATOR CLASS command.
+ *
+ * Verbose syntax
+ * CREATE OPERATOR CLASS %{identity}D %{default}s FOR TYPE %{type}T USING
+ * %{amname}I %{opfamily}s AS %{items:, }s
+ */
+static ObjTree *
+deparse_CreateOpClassStmt(CollectedCommand *cmd)
+{
+ Oid opcoid = cmd->d.createopc.address.objectId;
+ HeapTuple opcTup;
+ HeapTuple opfTup;
+ Form_pg_opfamily opfForm;
+ Form_pg_opclass opcForm;
+ ObjTree *ret;
+ ObjTree *tmp_obj;
+ List *list;
+ ListCell *cell;
+
+ /* Don't deparse SQL commands generated while creating extension */
+ if (cmd->in_extension)
+ return NULL;
+
+ opcTup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opcoid));
+ if (!HeapTupleIsValid(opcTup))
+ elog(ERROR, "cache lookup failed for opclass with OID %u", opcoid);
+ opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+
+ opfTup = SearchSysCache1(OPFAMILYOID, opcForm->opcfamily);
+ if (!HeapTupleIsValid(opfTup))
+ elog(ERROR, "cache lookup failed for operator family with OID %u", opcForm->opcfamily);
+ opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+ ret = new_objtree_VA("CREATE OPERATOR CLASS %{identity}D %{default}s FOR TYPE %{type}T USING %{amname}I", 4,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(opcForm->opcnamespace,
+ NameStr(opcForm->opcname)),
+ "default", ObjTypeString,
+ opcForm->opcdefault ? "DEFAULT" : "",
+ "type", ObjTypeObject,
+ new_objtree_for_type(opcForm->opcintype, -1),
+ "amname", ObjTypeString, get_am_name(opcForm->opcmethod));
+
+ /*
+ * Add the FAMILY clause, but if it has the same name and namespace as the
+ * opclass, then have it expand to empty because it would cause a failure
+ * if the opfamily was created internally.
+ */
+ tmp_obj = new_objtree_VA("FAMILY %{opfamily}D", 1,
+ "opfamily", ObjTypeObject,
+ new_objtree_for_qualname(opfForm->opfnamespace,
+ NameStr(opfForm->opfname)));
+
+ if (strcmp(NameStr(opfForm->opfname), NameStr(opcForm->opcname)) == 0 &&
+ opfForm->opfnamespace == opcForm->opcnamespace)
+ append_not_present(tmp_obj);
+
+ append_object_object(ret, "%{opfamily}s", tmp_obj);
+
+ /*
+ * Add the initial item list. Note we always add the STORAGE clause, even
+ * when it is implicit in the original command.
+ */
+ tmp_obj = new_objtree("STORAGE");
+ append_object_object(tmp_obj, "%{type}T",
+ new_objtree_for_type(OidIsValid(opcForm->opckeytype) ?
+ opcForm->opckeytype : opcForm->opcintype,
+ -1));
+ list = list_make1(new_object_object(tmp_obj));
+
+ /* Add the declared operators */
+ foreach(cell, cmd->d.createopc.operators)
+ {
+ OpFamilyMember *oper = lfirst(cell);
+
+ tmp_obj = new_objtree_VA("OPERATOR %{num}n %{operator}O(%{ltype}T, %{rtype}T)", 4,
+ "num", ObjTypeInteger, oper->number,
+ "operator", ObjTypeObject,
+ new_objtree_for_qualname_id(OperatorRelationId,
+ oper->object),
+ "ltype", ObjTypeObject,
+ new_objtree_for_type(oper->lefttype, -1),
+ "rtype", ObjTypeObject,
+ new_objtree_for_type(oper->righttype, -1));
+
+ /* Add the FOR SEARCH / FOR ORDER BY clause */
+ if (oper->sortfamily == InvalidOid)
+ append_string_object(tmp_obj, "%{purpose}s", "purpose",
+ "FOR SEARCH");
+ else
+ {
+ ObjTree *tmp_obj2;
+
+ tmp_obj2 = new_objtree("FOR ORDER BY %{opfamily}D");
+ append_object_object(tmp_obj2, "opfamily",
+ new_objtree_for_qualname_id(OperatorFamilyRelationId,
+ oper->sortfamily));
+ append_object_object(tmp_obj, "%{purpose}s", tmp_obj2);
+ }
+
+ list = lappend(list, new_object_object(tmp_obj));
+ }
+
+ /* Add the declared support functions */
+ foreach(cell, cmd->d.createopc.procedures)
+ {
+ OpFamilyMember *proc = lfirst(cell);
+ HeapTuple procTup;
+ Form_pg_proc procForm;
+ Oid *proargtypes;
+ List *arglist = NIL;
+ int i;
+
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc->object));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for procedure with OID %u",
+ proc->object);
+ procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+ tmp_obj = new_objtree_VA("FUNCTION %{num}n (%{ltype}T, %{rtype}T) %{function}D", 4,
+ "num", ObjTypeInteger, proc->number,
+ "ltype", ObjTypeObject,
+ new_objtree_for_type(proc->lefttype, -1),
+ "rtype", ObjTypeObject,
+ new_objtree_for_type(proc->righttype, -1),
+ "function", ObjTypeObject,
+ new_objtree_for_qualname(procForm->pronamespace,
+ NameStr(procForm->proname)));
+
+ proargtypes = procForm->proargtypes.values;
+ for (i = 0; i < procForm->pronargs; i++)
+ {
+ ObjTree *arg;
+
+ arg = new_objtree_for_type(proargtypes[i], -1);
+ arglist = lappend(arglist, new_object_object(arg));
+ }
+
+ append_format_string(tmp_obj, "(");
+ append_array_object(tmp_obj, "%{argtypes:, }T", arglist);
+ append_format_string(tmp_obj, ")");
+
+ ReleaseSysCache(procTup);
+
+ list = lappend(list, new_object_object(tmp_obj));
+ }
+
+ append_array_object(ret, "AS %{items:, }s", list);
+
+ ReleaseSysCache(opfTup);
+ ReleaseSysCache(opcTup);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateOpFamilyStmt (CREATE OPERATOR FAMILY)
+ *
+ * Given a operator family OID and the parse tree that created it, return an
+ * ObjTree representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE OPERATOR FAMILY %{identity}D USING %{amname}I
+ */
+static ObjTree *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+ HeapTuple opfTup;
+ HeapTuple amTup;
+ Form_pg_opfamily opfForm;
+ Form_pg_am amForm;
+ ObjTree *ret;
+
+ opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(opfTup))
+ elog(ERROR, "cache lookup failed for operator family with OID %u", objectId);
+ opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+ amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
+ if (!HeapTupleIsValid(amTup))
+ elog(ERROR, "cache lookup failed for access method with OID %u",
+ opfForm->opfmethod);
+ amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+ ret = new_objtree_VA("CREATE OPERATOR FAMILY %{identity}D USING %{amname}I", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(opfForm->opfnamespace,
+ NameStr(opfForm->opfname)),
+ "amname", ObjTypeString, NameStr(amForm->amname));
+
+ ReleaseSysCache(amTup);
+ ReleaseSysCache(opfTup);
+
+ return ret;
+}
+
+/*
+ * Add common clauses to CreatePolicy or AlterPolicy deparse objects.
+ */
+static void
+add_policy_clauses(ObjTree *ret, Oid policyOid, List *roles, bool do_qual,
+ bool do_with_check)
+{
+ Relation polRel = table_open(PolicyRelationId, AccessShareLock);
+ HeapTuple polTup = get_catalog_object_by_oid(polRel, Anum_pg_policy_oid, policyOid);
+ Form_pg_policy polForm;
+
+ if (!HeapTupleIsValid(polTup))
+ elog(ERROR, "cache lookup failed for policy with OID %u", policyOid);
+
+ polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+ /* Add the "ON table" clause */
+ append_object_object(ret, "ON %{table}D",
+ new_objtree_for_qualname_id(RelationRelationId,
+ polForm->polrelid));
+
+ /*
+ * Add the "TO role" clause, if any. In the CREATE case, it always
+ * contains at least PUBLIC, but in the ALTER case it might be empty.
+ */
+ if (roles)
+ {
+ List *list = NIL;
+ ListCell *cell;
+
+ foreach(cell, roles)
+ {
+ RoleSpec *spec = (RoleSpec *) lfirst(cell);
+
+ list = lappend(list,
+ new_object_object(new_objtree_for_rolespec(spec)));
+ }
+ append_array_object(ret, "TO %{role:, }R", list);
+ }
+ else
+ append_not_present(ret);
+
+ /* Add the USING clause, if any */
+ if (do_qual)
+ {
+ Datum deparsed;
+ Datum storedexpr;
+ bool isnull;
+
+ storedexpr = heap_getattr(polTup, Anum_pg_policy_polqual,
+ RelationGetDescr(polRel), &isnull);
+ if (isnull)
+ elog(ERROR, "null polqual expression in policy %u", policyOid);
+ deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+ append_string_object(ret, "USING (%{expression}s)", "expression",
+ TextDatumGetCString(deparsed));
+ }
+ else
+ append_not_present(ret);
+
+ /* Add the WITH CHECK clause, if any */
+ if (do_with_check)
+ {
+ Datum deparsed;
+ Datum storedexpr;
+ bool isnull;
+
+ storedexpr = heap_getattr(polTup, Anum_pg_policy_polwithcheck,
+ RelationGetDescr(polRel), &isnull);
+ if (isnull)
+ elog(ERROR, "null polwithcheck expression in policy %u", policyOid);
+ deparsed = DirectFunctionCall2(pg_get_expr, storedexpr, polForm->polrelid);
+ append_string_object(ret, "WITH CHECK (%{expression}s)",
+ "expression", TextDatumGetCString(deparsed));
+ }
+ else
+ append_not_present(ret);
+
+ relation_close(polRel, AccessShareLock);
+}
+
+/*
+ * Deparse a CreatePolicyStmt (CREATE POLICY)
+ *
+ * Given a policy OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE POLICY %{identity}I
+ */
+static ObjTree *
+deparse_CreatePolicyStmt(Oid objectId, Node *parsetree)
+{
+ CreatePolicyStmt *node = (CreatePolicyStmt *) parsetree;
+ ObjTree *ret;
+
+ ret = new_objtree_VA("CREATE POLICY %{identity}I", 1,
+ "identity", ObjTypeString, node->policy_name);
+
+ /* Add the rest of the stuff */
+ add_policy_clauses(ret, objectId, node->roles, node->qual != NULL,
+ node->with_check != NULL);
+
+ return ret;
+}
+
+/*
+ * Deparse a AlterPolicyStmt (ALTER POLICY)
+ *
+ * Given a policy OID and the parse tree of the ALTER POLICY command, return
+ * an ObjTree representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER POLICY %{identity}I
+ */
+static ObjTree *
+deparse_AlterPolicyStmt(Oid objectId, Node *parsetree)
+{
+ AlterPolicyStmt *node = (AlterPolicyStmt *) parsetree;
+ ObjTree *ret;
+
+ ret = new_objtree_VA("ALTER POLICY %{identity}I", 1,
+ "identity", ObjTypeString, node->policy_name);
+
+ /* Add the rest of the stuff */
+ add_policy_clauses(ret, objectId, node->roles, node->qual != NULL,
+ node->with_check != NULL);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateSchemaStmt.
+ *
+ * Given a schema OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE SCHEMA %{if_not_exists}s %{name}I AUTHORIZATION %{authorization}s
+*/
+static ObjTree *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+ CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+ ObjTree *ret;
+ ObjTree *auth;
+
+ ret = new_objtree_VA("CREATE SCHEMA %{if_not_exists}s %{name}I", 2,
+ "if_not_exists", ObjTypeString,
+ node->if_not_exists ? "IF NOT EXISTS" : "",
+ "name", ObjTypeString,
+ node->schemaname ? node->schemaname : "");
+
+ auth = new_objtree("AUTHORIZATION");
+ if (node->authrole)
+ append_string_object(auth, "%{authorization_role}I",
+ "authorization_role",
+ get_rolespec_name(node->authrole));
+ else
+ {
+ append_null_object(auth, "%{authorization_role}I");
+ append_not_present(auth);
+ }
+ append_object_object(ret, "%{authorization}s", auth);
+
+ return ret;
+}
+
+/*
+ * Return the default value of a domain.
+ */
+static char *
+DomainGetDefault(HeapTuple domTup, bool missing_ok)
+{
+ Datum def;
+ Node *defval;
+ char *defstr;
+ bool isnull;
+
+ def = SysCacheGetAttr(TYPEOID, domTup, Anum_pg_type_typdefaultbin,
+ &isnull);
+ if (isnull)
+ {
+ if (!missing_ok)
+ elog(ERROR, "domain \"%s\" does not have a default value",
+ NameStr(((Form_pg_type) GETSTRUCT(domTup))->typname));
+ else
+ return NULL;
+ }
+
+ defval = stringToNode(TextDatumGetCString(def));
+ defstr = deparse_expression(defval, NULL /* dpcontext? */ ,
+ false, false);
+
+ return defstr;
+}
+
+/*
+ * Deparse a AlterDomainStmt.
+ *
+ * Verbose syntax
+ * ALTER DOMAIN %{identity}D DROP DEFAULT
+ * OR
+ * ALTER DOMAIN %{identity}D SET DEFAULT %{default}s
+ * OR
+ * ALTER DOMAIN %{identity}D DROP NOT NULL
+ * OR
+ * ALTER DOMAIN %{identity}D SET NOT NULL
+ * OR
+ * ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}s %{definition}s
+ * OR
+ * ALTER DOMAIN %{identity}D DROP CONSTRAINT IF EXISTS %{constraint_name}s %{cascade}s
+ * OR
+ * ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}s
+ */
+static ObjTree *
+deparse_AlterDomainStmt(Oid objectId, Node *parsetree,
+ ObjectAddress constraintAddr)
+{
+ AlterDomainStmt *node = (AlterDomainStmt *) parsetree;
+ HeapTuple domTup;
+ Form_pg_type domForm;
+ ObjTree *ret;
+
+ domTup = SearchSysCache1(TYPEOID, objectId);
+ if (!HeapTupleIsValid(domTup))
+ elog(ERROR, "cache lookup failed for domain with OID %u", objectId);
+ domForm = (Form_pg_type) GETSTRUCT(domTup);
+
+ switch (node->subtype)
+ {
+ case 'T':
+ /* SET DEFAULT / DROP DEFAULT */
+ if (node->def == NULL)
+ ret = new_objtree_VA("ALTER DOMAIN %{identity}D DROP DEFAULT", 2,
+ "type", ObjTypeString, "drop default",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(domForm->typnamespace,
+ NameStr(domForm->typname)));
+ else
+ ret = new_objtree_VA("ALTER DOMAIN %{identity}D SET DEFAULT %{default}s", 3,
+ "type", ObjTypeString, "set default",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(domForm->typnamespace,
+ NameStr(domForm->typname)),
+ "default", ObjTypeString,
+ DomainGetDefault(domTup, false));
+ break;
+ case 'N':
+ /* DROP NOT NULL */
+ ret = new_objtree_VA("ALTER DOMAIN %{identity}D DROP NOT NULL", 2,
+ "type", ObjTypeString, "drop not null",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(domForm->typnamespace,
+ NameStr(domForm->typname)));
+ break;
+ case 'O':
+ /* SET NOT NULL */
+ ret = new_objtree_VA("ALTER DOMAIN %{identity}D SET NOT NULL", 2,
+ "type", ObjTypeString, "set not null",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(domForm->typnamespace,
+ NameStr(domForm->typname)));
+ break;
+ case 'C':
+ /*
+ * ADD CONSTRAINT. Only CHECK constraints are supported by
+ * domains
+ */
+ ret = new_objtree_VA("ALTER DOMAIN %{identity}D ADD CONSTRAINT %{constraint_name}s %{definition}s", 4,
+ "type", ObjTypeString, "add constraint",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(domForm->typnamespace,
+ NameStr(domForm->typname)),
+ "constraint_name", ObjTypeString,
+ get_constraint_name(constraintAddr.objectId),
+ "definition", ObjTypeString,
+ pg_get_constraintdef_string(constraintAddr.objectId));
+ break;
+ case 'V':
+ /* VALIDATE CONSTRAINT */
+ /*
+ * Process subtype=specific options. Validation a constraint
+ * requires its name.
+ */
+ ret = new_objtree_VA("ALTER DOMAIN %{identity}D VALIDATE CONSTRAINT %{constraint_name}s", 3,
+ "type", ObjTypeString, "validate constraint",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(domForm->typnamespace,
+ NameStr(domForm->typname)),
+ "constraint_name", ObjTypeString, node->name);
+ break;
+ case 'X':
+ {
+ ObjTree *tmp_obj;
+
+ /* DROP CONSTRAINT */
+ ret = new_objtree_VA("ALTER DOMAIN %{identity}D DROP CONSTRAINT IF EXISTS %{constraint_name}s", 3,
+ "type", ObjTypeString, "drop constraint",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(domForm->typnamespace,
+ NameStr(domForm->typname)),
+ "constraint_name", ObjTypeString, node->name);
+
+ tmp_obj = new_objtree_VA("CASCADE", 1,
+ "present", ObjTypeBool, node->behavior == DROP_CASCADE);
+
+ append_object_object(ret, "%{cascade}s", tmp_obj);
+ }
+ break;
+ default:
+ elog(ERROR, "invalid subtype %c", node->subtype);
+ }
+
+ /* Done */
+ ReleaseSysCache(domTup);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateStatsStmt.
+ *
+ * Given a statistics OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE STATISTICS %{if_not_exists}s %{identity}D ON %{expr:, }s FROM %{stat_table_identity}D
+ */
+static ObjTree *
+deparse_CreateStatisticsStmt(Oid objectId, Node *parsetree)
+{
+ CreateStatsStmt *node = (CreateStatsStmt *) parsetree;
+ Form_pg_statistic_ext statform;
+ ObjTree *ret;
+ HeapTuple tup;
+ Datum datum;
+ bool isnull;
+ List *statexprs = NIL;
+
+ tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for statistic with OID %u", objectId);
+
+ statform = (Form_pg_statistic_ext) GETSTRUCT(tup);
+
+ ret = new_objtree_VA("CREATE STATISTICS %{if_not_exists}s %{identity}D", 2,
+ "if_not_exists", ObjTypeString,
+ node->if_not_exists ? "IF NOT EXISTS" : "",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(statform->stxnamespace,
+ NameStr(statform->stxname)));
+
+ datum = SysCacheGetAttr(STATEXTOID, tup, Anum_pg_statistic_ext_stxexprs,
+ &isnull);
+ if (!isnull)
+ {
+ ListCell *lc;
+ Relation statsrel;
+ List *context;
+ List *exprs = NIL;
+ char *exprsString;
+
+ statsrel = relation_open(statform->stxrelid, AccessShareLock);
+ context = deparse_context_for(RelationGetRelationName(statsrel),
+ RelationGetRelid(statsrel));
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = (List *) stringToNode(exprsString);
+
+ foreach(lc, exprs)
+ {
+ Node *expr = (Node *) lfirst(lc);
+ char *statexpr;
+
+ statexpr = deparse_expression(expr, context, false, false);
+ statexprs = lappend(statexprs, new_string_object(statexpr));
+ }
+
+ append_array_object(ret, "ON %{expr:, }s", statexprs);
+ pfree(exprsString);
+ relation_close(statsrel, AccessShareLock);
+ }
+
+ datum = SysCacheGetAttr(STATEXTOID, tup, Anum_pg_statistic_ext_stxkeys,
+ &isnull);
+ if (!isnull)
+ {
+ int keyno;
+ char *attname;
+ List *statcols = NIL;
+ int2vector *indoption;
+
+ indoption = (int2vector *) DatumGetPointer(datum);
+
+ for (keyno = 0; keyno < indoption->dim1; keyno++)
+ {
+ attname = get_attname(statform->stxrelid, indoption->values[keyno],
+ false);
+ statcols = lappend(statcols, new_string_object(attname));
+ }
+
+ if (indoption->dim1)
+ {
+ /* Append a ',' if statexprs is present else append 'ON' */
+ append_string_object(ret, "%{comma}s", "comma",
+ statexprs ? "," : "ON");
+ append_array_object(ret, "%{cols:, }s", statcols);
+ }
+ }
+
+ append_object_object(ret, "FROM %{stat_table_identity}D",
+ new_objtree_for_qualname(get_rel_namespace(statform->stxrelid),
+ get_rel_name(statform->stxrelid)));
+
+ ReleaseSysCache(tup);
+
+ return ret;
+}
+
+/*
+ * Deparse an CreateForeignServerStmt (CREATE SERVER)
+ *
+ * Given a server OID and the parse tree that created it, return an ObjTree
+ * representing the alter command.
+ *
+ * Verbose syntax
+ * CREATE SERVER %{identity}I %{type}s %{version}s FOREIGN DATA WRAPPER %{fdw}I
+ * %{generic_options}s
+ */
+static ObjTree *
+deparse_CreateForeignServerStmt(Oid objectId, Node *parsetree)
+{
+ CreateForeignServerStmt *node = (CreateForeignServerStmt *) parsetree;
+ ObjTree *ret;
+ ObjTree *tmp;
+
+ ret = new_objtree_VA("CREATE SERVER %{identity}I", 1,
+ "identity", ObjTypeString, node->servername);
+
+ /* Add a TYPE clause, if any */
+ tmp = new_objtree("TYPE");
+ if (node->servertype)
+ append_string_object(tmp, "%{type}L", "type", node->servertype);
+ else
+ append_not_present(tmp);
+ append_object_object(ret, "%{type}s", tmp);
+
+ /* Add a VERSION clause, if any */
+ tmp = new_objtree("VERSION");
+ if (node->version)
+ append_string_object(tmp, "%{version}L", "version", node->version);
+ else
+ append_not_present(tmp);
+ append_object_object(ret, "%{version}s", tmp);
+
+ append_string_object(ret, "FOREIGN DATA WRAPPER %{fdw}I", "fdw",
+ node->fdwname);
+
+ /* Add an OPTIONS clause, if any */
+ append_object_object(ret, "%{generic_options}s",
+ deparse_FdwOptions(node->options, NULL, false));
+
+ return ret;
+}
+
+/*
+ * Deparse an AlterForeignServerStmt (ALTER SERVER)
+ *
+ * Given a server OID and the parse tree that created it, return an ObjTree
+ * representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER SERVER %{identity}I %{version}s %{generic_options}s
+ */
+static ObjTree *
+deparse_AlterForeignServerStmt(Oid objectId, Node *parsetree)
+{
+ AlterForeignServerStmt *node = (AlterForeignServerStmt *) parsetree;
+ ObjTree *tmp;
+
+ /* Add a VERSION clause, if any */
+ tmp = new_objtree("VERSION");
+ if (node->has_version && node->version)
+ append_string_object(tmp, "%{version}L", "version", node->version);
+ else if (node->has_version)
+ append_string_object(tmp, "version", "version", "NULL");
+ else
+ append_not_present(tmp);
+
+ return new_objtree_VA("ALTER SERVER %{identity}I %{version}s %{generic_options}s", 3,
+ "identity", ObjTypeString, node->servername,
+ "version", ObjTypeObject, tmp,
+ "generic_options", ObjTypeObject,
+ deparse_FdwOptions(node->options, NULL, false));
+}
+
+/*
+ * Deparse a CreatePLangStmt.
+ *
+ * Given a language OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE %{or_replace}s %{trusted}s LANGUAGE %{identity}s HANDLER %{handler}D
+ * %{inline}s %{validator}s
+ */
+static ObjTree *
+deparse_CreateLangStmt(Oid objectId, Node *parsetree)
+{
+ CreatePLangStmt *node = (CreatePLangStmt *) parsetree;
+ ObjTree *ret;
+ ObjTree *tmp;
+ HeapTuple langTup;
+ Form_pg_language langForm;
+
+ langTup = SearchSysCache1(LANGOID,
+ ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(langTup))
+ elog(ERROR, "cache lookup failed for language with OID %u", objectId);
+ langForm = (Form_pg_language) GETSTRUCT(langTup);
+
+ ret = new_objtree_VA("CREATE %{or_replace}s %{trusted}s LANGUAGE %{identity}s", 3,
+ "or_replace", ObjTypeString,
+ node->replace ? "OR REPLACE" : "",
+ "trusted", ObjTypeString,
+ langForm->lanpltrusted ? "TRUSTED" : "",
+ "identity", ObjTypeString, node->plname);
+
+ if (node->plhandler != NIL)
+ {
+ /* Add the HANDLER clause */
+ append_object_object(ret, "HANDLER %{handler}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ langForm->lanplcallfoid));
+
+ /* Add the INLINE clause, if any */
+ tmp = new_objtree("INLINE");
+ if (OidIsValid(langForm->laninline))
+ {
+ append_object_object(tmp, "%{handler_name}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ langForm->laninline));
+ }
+ else
+ append_not_present(tmp);
+ append_object_object(ret, "%{inline}s", tmp);
+
+ /* Add the VALIDATOR clause, if any */
+ tmp = new_objtree("VALIDATOR");
+ if (OidIsValid(langForm->lanvalidator))
+ {
+ append_object_object(tmp, "%{handler_name}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ langForm->lanvalidator));
+ }
+ else
+ append_not_present(tmp);
+ append_object_object(ret, "%{validator}s", tmp);
+ }
+
+ ReleaseSysCache(langTup);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateForeignTableStmt (CREATE FOREIGN TABLE).
+ *
+ * Given a table OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * CREATE FOREIGN TABLE %{if_not_exists}s %{identity}D
+ * [(%{table_elements:, }s) %{inherits}s
+ * | PARTITION OF %{parent_identity}D (%{typed_table_elements:, }s) %{partition_bound}s]
+ * SERVER %{server}I %{generic_options}s
+ */
+static ObjTree *
+deparse_CreateForeignTableStmt(Oid objectId, Node *parsetree)
+{
+ CreateForeignTableStmt *stmt = (CreateForeignTableStmt *) parsetree;
+ Relation relation = relation_open(objectId, AccessShareLock);
+ List *dpcontext;
+ ObjTree *createStmt;
+ ObjTree *tmpobj;
+ List *tableelts = NIL;
+
+ createStmt = new_objtree("CREATE FOREIGN TABLE");
+
+ append_string_object(createStmt, "%{if_not_exists}s", "if_not_exists",
+ stmt->base.if_not_exists ? "IF NOT EXISTS" : "");
+
+ tmpobj = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+ RelationGetRelationName(relation));
+ append_object_object(createStmt, "%{identity}D", tmpobj);
+
+ dpcontext = deparse_context_for(RelationGetRelationName(relation),
+ objectId);
+ if (stmt->base.partbound)
+ {
+ /* Partitioned table */
+ List *parents;
+ ObjElem *elem;
+
+ parents = deparse_InhRelations(objectId);
+ elem = (ObjElem *) linitial(parents);
+
+ Assert(list_length(parents) == 1);
+
+ append_format_string(createStmt, "PARTITION OF");
+
+ append_object_object(createStmt, "%{parent_identity}D",
+ elem->value.object);
+
+ tableelts = deparse_TableElements(relation, stmt->base.tableElts, dpcontext,
+ true, /* typed table */
+ false); /* not composite */
+ tableelts = obtainConstraints(tableelts, objectId, InvalidOid,
+ ConstrObjForeignTable);
+
+ tmpobj = new_objtree("");
+ if (tableelts)
+ append_array_object(tmpobj, "(%{elements:, }s)", tableelts);
+ else
+ append_not_present(tmpobj);
+
+ append_object_object(createStmt, "%{typed_table_elements}s", tmpobj);
+
+ /*
+ * Add the partition_bound_spec, i.e. the FOR VALUES clause.
+ * Get pg_class.relpartbound. We cannot use partbound in the parsetree
+ * directly as it's the original partbound expression which haven't
+ * been transformed.
+ */
+ append_string_object(createStmt, "%{partition_bound}s", "partition_bound",
+ RelationGetPartitionBound(objectId));
+
+ /* No PARTITION BY clause for CREATE FOREIGN TABLE */
+ }
+ else
+ {
+ tableelts = deparse_TableElements(relation, stmt->base.tableElts, dpcontext,
+ false, /* not typed table */
+ false); /* not composite */
+ tableelts = obtainConstraints(tableelts, objectId, InvalidOid,
+ ConstrObjForeignTable);
+
+ if (tableelts)
+ append_array_object(createStmt, "(%{table_elements:, }s)", tableelts);
+ else
+ append_format_string(createStmt, "()");
+
+ /*
+ * 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.
+ */
+ tmpobj = new_objtree("INHERITS");
+ if (stmt->base.inhRelations != NIL)
+ append_array_object(tmpobj, "(%{parents:, }D)", deparse_InhRelations(objectId));
+ else
+ {
+ append_null_object(tmpobj, "(%{parents:, }D)");
+ append_not_present(tmpobj);
+ }
+ append_object_object(createStmt, "%{inherits}s", tmpobj);
+ }
+
+ append_string_object(createStmt, "SERVER %{server}I", "server", stmt->servername);
+
+ /* add an OPTIONS clause, if any */
+ append_object_object(createStmt, "%{generic_options}s",
+ deparse_FdwOptions(stmt->options, NULL, false));
+
+ relation_close(relation, AccessShareLock);
+
+ return createStmt;
+}
+
+/*
+ * Deparse a DefineStmt.
+ */
+static ObjTree *
+deparse_DefineStmt(Oid objectId, Node *parsetree, ObjectAddress secondaryObj)
+{
+ DefineStmt *define = (DefineStmt *) parsetree;
+
+ switch (define->kind)
+ {
+ case OBJECT_AGGREGATE:
+ return deparse_DefineStmt_Aggregate(objectId, define);
+
+ case OBJECT_COLLATION:
+ return deparse_DefineStmt_Collation(objectId, define, secondaryObj);
+
+ case OBJECT_OPERATOR:
+ return deparse_DefineStmt_Operator(objectId, define);
+
+ case OBJECT_TSCONFIGURATION:
+ return deparse_DefineStmt_TSConfig(objectId, define, secondaryObj);
+
+ case OBJECT_TSDICTIONARY:
+ return deparse_DefineStmt_TSDictionary(objectId, define);
+
+ case OBJECT_TSPARSER:
+ return deparse_DefineStmt_TSParser(objectId, define);
+
+ case OBJECT_TSTEMPLATE:
+ return deparse_DefineStmt_TSTemplate(objectId, define);
+
+ case OBJECT_TYPE:
+ return deparse_DefineStmt_Type(objectId, define);
+
+ default:
+ elog(ERROR, "unsupported object kind: %d", define->kind);
+ }
+
+ return NULL;
+}
+
+/*
+ * Form an ObjElem to be used as a single argument in an aggregate argument
+ * list
+ */
+static ObjElem *
+form_agg_argument(int idx, char *modes, char **names, Oid *types)
+{
+ ObjTree *arg;
+
+ arg = new_objtree("");
+
+ append_string_object(arg, "%{mode}s", "mode",
+ (modes && modes[idx] == 'v') ? "VARIADIC" : "");
+ append_string_object(arg, "%{name}s", "name", names ? names[idx] : "");
+ append_object_object(arg, "%{type}T",
+ new_objtree_for_type(types[idx], -1));
+
+ return new_object_object(arg);
+}
+
+/*
+ * Deparse a DefineStmt (CREATE AGGREGATE)
+ *
+ * Given a aggregate OID, return an ObjTree representing the CREATE command.
+ *
+ * Verbose syntax
+ * CREATE %{or_replace}s AGGREGATE %{identity}D(%{types}s) (%{elems:, }s)
+ */
+static ObjTree *
+deparse_DefineStmt_Aggregate(Oid objectId, DefineStmt *define)
+{
+ HeapTuple aggTup;
+ HeapTuple procTup;
+ ObjTree *stmt;
+ ObjTree *tmp;
+ List *list;
+ Datum initval;
+ bool isnull;
+ Form_pg_aggregate agg;
+ Form_pg_proc proc;
+ Form_pg_operator op;
+ HeapTuple tup;
+
+ aggTup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(aggTup))
+ elog(ERROR, "cache lookup failed for aggregate with OID %u", objectId);
+ agg = (Form_pg_aggregate) GETSTRUCT(aggTup);
+
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(agg->aggfnoid));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for procedure with OID %u",
+ agg->aggfnoid);
+ proc = (Form_pg_proc) GETSTRUCT(procTup);
+
+ stmt = new_objtree_VA("CREATE %{or_replace}s AGGREGATE", 1,
+ "or_replace", ObjTypeString, define->replace ? "OR REPLACE" : "");
+
+ append_object_object(stmt, "%{identity}D",
+ new_objtree_for_qualname(proc->pronamespace,
+ NameStr(proc->proname)));
+
+ /*
+ * Add the argument list. There are three cases to consider:
+ *
+ * 1. no arguments, in which case the signature is (*).
+ *
+ * 2. Not an ordered-set aggregate. In this case there's one or more
+ * arguments.
+ *
+ * 3. Ordered-set aggregates. These have zero or more direct arguments, and
+ * one or more ordered arguments.
+ *
+ * We don't need to consider default values or table parameters, and the
+ * only mode that we need to consider is VARIADIC.
+ */
+
+ if (proc->pronargs == 0)
+ append_string_object(stmt, "(%{types}s)", "types", "*");
+ else if (!AGGKIND_IS_ORDERED_SET(agg->aggkind))
+ {
+ int i;
+ int nargs;
+ Oid *types;
+ char *modes;
+ char **names;
+
+ nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+ /* only direct arguments in this case */
+ list = NIL;
+ for (i = 0; i < nargs; i++)
+ {
+ list = lappend(list, form_agg_argument(i, modes, names, types));
+ }
+
+ tmp = new_objtree_VA("%{direct:, }s", 1,
+ "direct", ObjTypeArray, list);
+ append_object_object(stmt, "(%{types}s)", tmp);
+ }
+ else
+ {
+ int i;
+ int nargs;
+ Oid *types;
+ char *modes;
+ char **names;
+
+ nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+ /* direct arguments ... */
+ list = NIL;
+ for (i = 0; i < agg->aggnumdirectargs; i++)
+ {
+ list = lappend(list, form_agg_argument(i, modes, names, types));
+ }
+ tmp = new_objtree_VA("%{direct:, }s", 1,
+ "direct", ObjTypeArray, list);
+
+ /*
+ * ... and aggregated arguments. If the last direct argument is
+ * VARIADIC, we need to repeat it here rather than searching for more
+ * arguments. (See aggr_args production in gram.y for an explanation.)
+ */
+ if (modes && modes[agg->aggnumdirectargs - 1] == 'v')
+ {
+ list = list_make1(form_agg_argument(agg->aggnumdirectargs - 1,
+ modes, names, types));
+ }
+ else
+ {
+ list = NIL;
+ for (i = agg->aggnumdirectargs; i < nargs; i++)
+ {
+ list = lappend(list, form_agg_argument(i, modes, names, types));
+ }
+ }
+ append_array_object(tmp, "ORDER BY %{aggregated:, }s", list);
+
+ append_object_object(stmt, "(%{types}s)", tmp);
+ }
+
+ /* Add the definition clause */
+ list = NIL;
+
+ /* SFUNC */
+ tmp = new_objtree_VA("SFUNC=%{procedure}D", 1,
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId, agg->aggtransfn));
+ list = lappend(list, new_object_object(tmp));
+
+ /* STYPE */
+ tmp = new_objtree_VA("STYPE=%{type}T", 1,
+ "type", ObjTypeObject,
+ new_objtree_for_type(agg->aggtranstype, -1));
+ list = lappend(list, new_object_object(tmp));
+
+ /* SSPACE */
+ tmp = new_objtree("SSPACE=");
+ if (agg->aggtransspace != 0)
+ append_int_object(tmp,"%{space}n", agg->aggtransspace);
+ else
+ append_not_present(tmp);
+
+ list = lappend(list, new_object_object(tmp));
+
+ /* FINALFUNC */
+ tmp = new_objtree("FINALFUNC=");
+ if (OidIsValid(agg->aggfinalfn))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ agg->aggfinalfn));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* FINALFUNC_EXTRA */
+ tmp = new_objtree("FINALFUNC_EXTRA=");
+ if (agg->aggfinalextra)
+ append_string_object(tmp, "%{value}s", "value", "true");
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* INITCOND */
+ initval = SysCacheGetAttr(AGGFNOID, aggTup,
+ Anum_pg_aggregate_agginitval,
+ &isnull);
+ tmp = new_objtree("INITCOND=");
+ if (!isnull)
+ append_string_object(tmp,"%{initval}L", "initval", TextDatumGetCString(initval));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* MSFUNC */
+ tmp = new_objtree("MSFUNC=");
+ if (OidIsValid(agg->aggmtransfn))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId, agg->aggmtransfn));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* MSTYPE */
+ tmp = new_objtree("MSTYPE=");
+ if (OidIsValid(agg->aggmtranstype))
+ append_object_object(tmp, "%{type}T", new_objtree_for_type(agg->aggmtranstype, -1));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* MSSPACE */
+ tmp = new_objtree("MSSPACE=");
+ if (agg->aggmtransspace != 0)
+ append_int_object(tmp, "%{space}n", agg->aggmtransspace);
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* MINVFUNC */
+ tmp = new_objtree("MINVFUNC=");
+ if (OidIsValid(agg->aggminvtransfn))
+ append_object_object(tmp, "%{type}T", new_objtree_for_qualname_id(ProcedureRelationId,
+ agg->aggminvtransfn));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* MFINALFUNC */
+ tmp = new_objtree("MFINALFUNC=");
+ if (OidIsValid(agg->aggmfinalfn))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ agg->aggmfinalfn));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* MFINALFUNC_EXTRA */
+ tmp = new_objtree("MFINALFUNC_EXTRA=");
+ if (agg->aggmfinalextra)
+ append_string_object(tmp, "%{value}s", "value", "true");
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* MINITCOND */
+ initval = SysCacheGetAttr(AGGFNOID, aggTup,
+ Anum_pg_aggregate_aggminitval,
+ &isnull);
+ tmp = new_objtree("MINITCOND=");
+ if (!isnull)
+ append_string_object(tmp, "%{initval}L", "initval", TextDatumGetCString(initval));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* HYPOTHETICAL */
+ tmp = new_objtree("HYPOTHETICAL=");
+ if (agg->aggkind == AGGKIND_HYPOTHETICAL)
+ append_string_object(tmp, "%{value}s", "value", "true");
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* SORTOP */
+ tmp = new_objtree("SORTOP=");
+ if (OidIsValid(agg->aggsortop))
+ {
+ tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(agg->aggsortop));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for operator with OID %u", agg->aggsortop);
+ op = (Form_pg_operator) GETSTRUCT(tup);
+ append_object_object(tmp, "%{operator}D",
+ new_objtree_for_qualname(op->oprnamespace,
+ NameStr(op->oprname)));
+
+ ReleaseSysCache(tup);
+ }
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* Done with the definition clause */
+ append_array_object(stmt, "(%{elems:, }s)", list);
+
+ ReleaseSysCache(procTup);
+ ReleaseSysCache(aggTup);
+
+ return stmt;
+}
+
+/*
+ * Deparse a DefineStmt (CREATE COLLATION)
+ *
+ * Given a collation OID, return an ObjTree representing the CREATE command.
+ *
+ * Verbose syntax
+ * CREATE COLLATION %{if_not_exists}s %{identity}D FROM %{from_identity}D
+ * (%{elems:, }s)
+ */
+static ObjTree *
+deparse_DefineStmt_Collation(Oid objectId, DefineStmt *define,
+ ObjectAddress fromCollid)
+{
+ ObjTree *ret;
+ HeapTuple colTup;
+ Form_pg_collation colForm;
+ Datum datum;
+ bool isnull;
+ ObjTree *tmp;
+ List *list = NIL;
+
+ colTup = SearchSysCache1(COLLOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(colTup))
+ elog(ERROR, "cache lookup failed for collation with OID %u", objectId);
+ colForm = (Form_pg_collation) GETSTRUCT(colTup);
+
+ ret = new_objtree_VA("CREATE COLLATION %{if_not_exists}s %{identity}D", 2,
+ "if_not_exists", ObjTypeString,
+ define->if_not_exists ? "IF NOT EXISTS" : "",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(colForm->collnamespace,
+ NameStr(colForm->collname)));
+
+ if (OidIsValid(fromCollid.objectId))
+ {
+ Oid collid = fromCollid.objectId;
+ HeapTuple tp;
+ Form_pg_collation fromColForm;
+
+ /*
+ * CREATE COLLATION %{identity}D FROM existing_collation;
+ */
+ tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for collation with OID %u", collid);
+
+ fromColForm = (Form_pg_collation) GETSTRUCT(tp);
+
+ append_object_object(ret, "FROM %{from_identity}D",
+ new_objtree_for_qualname(fromColForm->collnamespace,
+ NameStr(fromColForm->collname)));
+
+ ReleaseSysCache(tp);
+ ReleaseSysCache(colTup);
+ return ret;
+ }
+
+ /* LOCALE */
+ datum = SysCacheGetAttr(COLLOID, colTup, Anum_pg_collation_colliculocale, &isnull);
+ if (!isnull)
+ {
+ tmp = new_objtree_VA("LOCALE=%{locale}L", 2,
+ "clause", ObjTypeString, "locale",
+ "locale", ObjTypeString,
+ psprintf("%s", TextDatumGetCString(datum)));
+ list = lappend(list, new_object_object(tmp));
+ }
+
+ /* LC_COLLATE */
+ datum = SysCacheGetAttr(COLLOID, colTup, Anum_pg_collation_collcollate, &isnull);
+ if (!isnull)
+ {
+ tmp = new_objtree_VA("LC_COLLATE=%{collate}L", 2,
+ "clause", ObjTypeString, "collate",
+ "collate", ObjTypeString,
+ psprintf("%s", TextDatumGetCString(datum)));
+ list = lappend(list, new_object_object(tmp));
+ }
+
+ /* LC_CTYPE */
+ datum = SysCacheGetAttr(COLLOID, colTup, Anum_pg_collation_collctype, &isnull);
+ if (!isnull)
+ {
+ tmp = new_objtree_VA("LC_CTYPE=%{ctype}L", 2,
+ "clause", ObjTypeString, "ctype",
+ "ctype", ObjTypeString,
+ psprintf("%s", TextDatumGetCString(datum)));
+ list = lappend(list, new_object_object(tmp));
+ }
+
+ /* PROVIDER */
+ if (colForm->collprovider == COLLPROVIDER_ICU)
+ {
+ tmp = new_objtree_VA("PROVIDER=%{provider}L", 2,
+ "clause", ObjTypeString, "provider",
+ "provider", ObjTypeString,
+ psprintf("%s", "icu"));
+ list = lappend(list, new_object_object(tmp));
+ }
+ else if (colForm->collprovider == COLLPROVIDER_LIBC)
+ {
+ tmp = new_objtree_VA("PROVIDER=%{provider}L", 2,
+ "clause", ObjTypeString, "provider",
+ "provider", ObjTypeString,
+ psprintf("%s", "libc"));
+ list = lappend(list, new_object_object(tmp));
+ }
+
+ /* DETERMINISTIC */
+ if (colForm->collisdeterministic)
+ {
+ tmp = new_objtree_VA("DETERMINISTIC=%{deterministic}L", 2,
+ "clause", ObjTypeString, "deterministic",
+ "deterministic", ObjTypeString,
+ psprintf("%s", "true"));
+ list = lappend(list, new_object_object(tmp));
+ }
+
+ /* VERSION */
+ datum = SysCacheGetAttr(COLLOID, colTup, Anum_pg_collation_collversion, &isnull);
+ if (!isnull)
+ {
+ tmp = new_objtree_VA("VERSION=%{version}L", 2,
+ "clause", ObjTypeString, "version",
+ "version", ObjTypeString,
+ psprintf("%s", TextDatumGetCString(datum)));
+ list = lappend(list, new_object_object(tmp));
+ }
+
+ append_array_object(ret, "(%{elems:, }s)", list);
+ ReleaseSysCache(colTup);
+
+ return ret;
+}
+
+/*
+ * Deparse a DefineStmt (CREATE OPERATOR)
+ *
+ * Given a operator OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE OPERATOR %{identity}O (%{elems:, }s)
+ */
+static ObjTree *
+deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define)
+{
+ HeapTuple oprTup;
+ ObjTree *ret;
+ ObjTree *tmp_obj;
+ List *list = NIL;
+ Form_pg_operator oprForm;
+
+ oprTup = SearchSysCache1(OPEROID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(oprTup))
+ elog(ERROR, "cache lookup failed for operator with OID %u", objectId);
+ oprForm = (Form_pg_operator) GETSTRUCT(oprTup);
+
+ ret = new_objtree_VA("CREATE OPERATOR %{identity}O", 1,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(oprForm->oprnamespace,
+ NameStr(oprForm->oprname)));
+
+ /* PROCEDURE */
+ tmp_obj = new_objtree_VA("PROCEDURE=%{procedure}D", 2,
+ "clause", ObjTypeString, "procedure",
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ oprForm->oprcode));
+ list = lappend(list, new_object_object(tmp_obj));
+
+ /* LEFTARG */
+ tmp_obj = new_objtree_VA("LEFTARG=", 1,
+ "clause", ObjTypeString, "leftarg");
+ if (OidIsValid(oprForm->oprleft))
+ append_object_object(tmp_obj, "%{type}T",
+ new_objtree_for_type(oprForm->oprleft, -1));
+ else
+ append_not_present(tmp_obj);
+ list = lappend(list, new_object_object(tmp_obj));
+
+ /* RIGHTARG */
+ tmp_obj = new_objtree_VA("RIGHTARG=", 1,
+ "clause", ObjTypeString, "rightarg");
+ if (OidIsValid(oprForm->oprright))
+ append_object_object(tmp_obj, "%{type}T",
+ new_objtree_for_type(oprForm->oprright, -1));
+ else
+ append_not_present(tmp_obj);
+ list = lappend(list, new_object_object(tmp_obj));
+
+ /* COMMUTATOR */
+ tmp_obj = new_objtree_VA("COMMUTATOR=", 1,
+ "clause", ObjTypeString, "commutator");
+ if (OidIsValid(oprForm->oprcom))
+ append_object_object(tmp_obj, "%{oper}D",
+ new_objtree_for_qualname_id(OperatorRelationId,
+ oprForm->oprcom));
+ else
+ append_not_present(tmp_obj);
+ list = lappend(list, new_object_object(tmp_obj));
+
+ /* NEGATOR */
+ tmp_obj = new_objtree_VA("NEGATOR=", 1,
+ "clause", ObjTypeString, "negator");
+ if (OidIsValid(oprForm->oprnegate))
+ append_object_object(tmp_obj, "%{oper}D",
+ new_objtree_for_qualname_id(OperatorRelationId,
+ oprForm->oprnegate));
+ else
+ append_not_present(tmp_obj);
+ list = lappend(list, new_object_object(tmp_obj));
+
+ /* RESTRICT */
+ tmp_obj = new_objtree_VA("RESTRICT=", 1,
+ "clause", ObjTypeString, "restrict");
+ if (OidIsValid(oprForm->oprrest))
+ append_object_object(tmp_obj, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ oprForm->oprrest));
+ else
+ append_not_present(tmp_obj);
+ list = lappend(list, new_object_object(tmp_obj));
+
+ /* JOIN */
+ tmp_obj = new_objtree_VA("JOIN=", 1,
+ "clause", ObjTypeString, "join");
+ if (OidIsValid(oprForm->oprjoin))
+ append_object_object(tmp_obj, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ oprForm->oprjoin));
+ else
+ append_not_present(tmp_obj);
+ list = lappend(list, new_object_object(tmp_obj));
+
+ /* HASHES */
+ tmp_obj = new_objtree_VA("HASHES", 1,
+ "clause", ObjTypeString, "hashes");
+ if (!oprForm->oprcanhash)
+ append_not_present(tmp_obj);
+ list = lappend(list, new_object_object(tmp_obj));
+
+ /* MERGES */
+ tmp_obj = new_objtree_VA("MERGES", 1,
+ "clause", ObjTypeString, "merges");
+ if (!oprForm->oprcanmerge)
+ append_not_present(tmp_obj);
+ list = lappend(list, new_object_object(tmp_obj));
+
+ append_array_object(ret, "(%{elems:, }s)", list);
+
+ ReleaseSysCache(oprTup);
+
+ return ret;
+}
+
+/*
+ * Deparse a CREATE TYPE statement.
+ *
+ * Verbose syntax
+ * CREATE TYPE %{identity}D %{elems:, }s)
+ */
+static ObjTree *
+deparse_DefineStmt_Type(Oid objectId, DefineStmt *define)
+{
+ HeapTuple typTup;
+ ObjTree *ret;
+ ObjTree *tmp;
+ List *list = NIL;
+ char *str;
+ Datum dflt;
+ bool isnull;
+ Form_pg_type typForm;
+
+ typTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(typTup))
+ elog(ERROR, "cache lookup failed for type with OID %u", objectId);
+ typForm = (Form_pg_type) GETSTRUCT(typTup);
+
+ ret = new_objtree_VA("CREATE TYPE %{identity}D", 1,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(typForm->typnamespace,
+ NameStr(typForm->typname)));
+
+ /* Shell types. */
+ if (!typForm->typisdefined)
+ {
+ ReleaseSysCache(typTup);
+ return ret;
+ }
+
+ /* Add the definition clause */
+ /* INPUT */
+ tmp = new_objtree_VA("(INPUT=%{procedure}D", 2,
+ "clause", ObjTypeString, "input",
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typinput));
+ list = lappend(list, new_object_object(tmp));
+
+ /* OUTPUT */
+ tmp = new_objtree_VA("OUTPUT=%{procedure}D", 2,
+ "clause", ObjTypeString, "output",
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typoutput));
+ list = lappend(list, new_object_object(tmp));
+
+ /* RECEIVE */
+ tmp = new_objtree_VA("RECEIVE=", 1,
+ "clause", ObjTypeString, "receive");
+ if (OidIsValid(typForm->typreceive))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typreceive));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* SEND */
+ tmp = new_objtree_VA("SEND=", 1,
+ "clause", ObjTypeString, "send");
+ if (OidIsValid(typForm->typsend))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typsend));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* TYPMOD_IN */
+ tmp = new_objtree_VA("TYPMOD_IN=", 1,
+ "clause", ObjTypeString, "typmod_in");
+ if (OidIsValid(typForm->typmodin))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typmodin));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* TYPMOD_OUT */
+ tmp = new_objtree_VA("TYPMOD_OUT=", 1,
+ "clause", ObjTypeString, "typmod_out");
+ if (OidIsValid(typForm->typmodout))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typmodout));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* ANALYZE */
+ tmp = new_objtree_VA("ANALYZE=", 1,
+ "clause", ObjTypeString, "analyze");
+ if (OidIsValid(typForm->typanalyze))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ typForm->typanalyze));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* INTERNALLENGTH */
+ if (typForm->typlen == -1)
+ tmp = new_objtree("INTERNALLENGTH=VARIABLE");
+ else
+ tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}n", 1,
+ "typlen", ObjTypeInteger, typForm->typlen);
+
+ list = lappend(list, new_object_object(tmp));
+
+ /* PASSEDBYVALUE */
+ tmp = new_objtree_VA("PASSEDBYVALUE", 1,
+ "clause", ObjTypeString, "passedbyvalue");
+ if (!typForm->typbyval)
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* XXX it's odd to represent alignment with schema-qualified type names */
+ switch (typForm->typalign)
+ {
+ case 'd':
+ str = "pg_catalog.float8";
+ break;
+ case 'i':
+ str = "pg_catalog.int4";
+ break;
+ case 's':
+ str = "pg_catalog.int2";
+ break;
+ case 'c':
+ str = "pg_catalog.bpchar";
+ break;
+ default:
+ elog(ERROR, "invalid alignment %c", typForm->typalign);
+ }
+
+ /* ALIGNMENT */
+ tmp = new_objtree_VA("ALIGNMENT=%{align}s", 2,
+ "clause", ObjTypeString, "alignment",
+ "align", ObjTypeString, str);
+ list = lappend(list, new_object_object(tmp));
+
+ /* STORAGE */
+ tmp = new_objtree_VA("STORAGE=%{storage}s", 2,
+ "clause", ObjTypeString, "storage",
+ "storage", ObjTypeString,
+ get_type_storage(typForm->typstorage));
+ list = lappend(list, new_object_object(tmp));
+
+ /* CATEGORY */
+ tmp = new_objtree_VA("CATEGORY=%{category}s", 2,
+ "clause", ObjTypeString, "category",
+ "category", ObjTypeString,
+ psprintf("%c", typForm->typcategory));
+ list = lappend(list, new_object_object(tmp));
+
+ /* PREFERRED */
+ tmp = new_objtree_VA("PREFERRED=%{preferred}s", 1,
+ "preferred", ObjTypeString,
+ typForm->typispreferred ? "TRUE" : "FALSE");
+ if (!typForm->typispreferred)
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* DEFAULT */
+ dflt = SysCacheGetAttr(TYPEOID, typTup,
+ Anum_pg_type_typdefault,
+ &isnull);
+ tmp = new_objtree_VA("DEFAULT=", 1,
+ "clause", ObjTypeString, "default");
+ if (!isnull)
+ append_string_object(tmp, "%{default}s", "default",
+ TextDatumGetCString(dflt));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* ELEMENT */
+ tmp = new_objtree_VA("ELEMENT=", 1,
+ "clause", ObjTypeString, "element");
+ if (OidIsValid(typForm->typelem))
+ append_object_object(tmp, "%{elem}T",
+ new_objtree_for_type(typForm->typelem, -1));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* DELIMITER */
+ tmp = new_objtree_VA("DELIMITER=", 1,
+ "clause", ObjTypeString, "delimiter");
+ append_string_object(tmp, "%{delim}L", "delim",
+ psprintf("%c", typForm->typdelim));
+ list = lappend(list, new_object_object(tmp));
+
+ /* COLLATABLE */
+ tmp = new_objtree_VA("COLLATABLE=", 1,
+ "clause", ObjTypeString, "collatable");
+ if (!OidIsValid(typForm->typcollation))
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ append_array_object(ret, "%{elems:, }s)", list);
+
+ ReleaseSysCache(typTup);
+
+ return ret;
+}
+
+/*
+ * Deparse a CREATE TEXT SEARCH CONFIGURATION statement.
+ *
+ * Verbose syntax
+ * CREATE TEXT SEARCH CONFIGURATION %{identity}D (%{elems:, }s)
+ */
+static ObjTree *
+deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define,
+ ObjectAddress copied)
+{
+ HeapTuple tscTup;
+ HeapTuple tspTup;
+ ObjTree *ret;
+ ObjTree *tmp;
+ Form_pg_ts_config tscForm;
+ Form_pg_ts_parser tspForm;
+ List *list = NIL;
+
+ tscTup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tscTup))
+ elog(ERROR, "cache lookup failed for text search configuration with OID %u",
+ objectId);
+ tscForm = (Form_pg_ts_config) GETSTRUCT(tscTup);
+
+ tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(tscForm->cfgparser));
+ if (!HeapTupleIsValid(tspTup))
+ elog(ERROR, "cache lookup failed for text search parser %u",
+ tscForm->cfgparser);
+ tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup);
+
+ /*
+ * Add the definition clause. If we have COPY'ed an existing config, add
+ * a COPY clause; otherwise add a PARSER clause.
+ */
+ /* COPY */
+ tmp = new_objtree_VA("COPY=", 1,
+ "clause", ObjTypeString, "copy");
+ if (OidIsValid(copied.objectId))
+ append_object_object(tmp, "%{tsconfig}D",
+ new_objtree_for_qualname_id(TSConfigRelationId,
+ copied.objectId));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* PARSER */
+ tmp = new_objtree_VA("PARSER=", 1,
+ "clause", ObjTypeString, "parser");
+ if (copied.objectId == InvalidOid)
+ append_object_object(tmp, "%{parser}D",
+ new_objtree_for_qualname(tspForm->prsnamespace,
+ NameStr(tspForm->prsname)));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ ret = new_objtree_VA("CREATE TEXT SEARCH CONFIGURATION %{identity}D (%{elems:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(tscForm->cfgnamespace,
+ NameStr(tscForm->cfgname)),
+ "elems", ObjTypeArray, list);
+
+ ReleaseSysCache(tspTup);
+ ReleaseSysCache(tscTup);
+ return ret;
+}
+
+/*
+ * Deparse a CREATE TEXT SEARCH PARSER statement.
+ *
+ * Verbose syntax
+ * CREATE TEXT SEARCH PARSER %{identity}D (%{elems:, }s)
+ */
+static ObjTree *
+deparse_DefineStmt_TSParser(Oid objectId, DefineStmt *define)
+{
+ HeapTuple tspTup;
+ ObjTree *ret;
+ ObjTree *tmp;
+ List *list = NIL;
+ Form_pg_ts_parser tspForm;
+
+ tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tspTup))
+ elog(ERROR, "cache lookup failed for text search parser with OID %u",
+ objectId);
+ tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup);
+
+ /* Add the definition clause */
+ /* START */
+ tmp = new_objtree_VA("START=%{procedure}D", 2,
+ "clause", ObjTypeString, "start",
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ tspForm->prsstart));
+ list = lappend(list, new_object_object(tmp));
+
+ /* GETTOKEN */
+ tmp = new_objtree_VA("GETTOKEN=%{procedure}D", 2,
+ "clause", ObjTypeString, "gettoken",
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ tspForm->prstoken));
+ list = lappend(list, new_object_object(tmp));
+
+ /* END */
+ tmp = new_objtree_VA("END=%{procedure}D", 2,
+ "clause", ObjTypeString, "end",
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ tspForm->prsend));
+ list = lappend(list, new_object_object(tmp));
+
+ /* LEXTYPES */
+ tmp = new_objtree_VA("LEXTYPES=%{procedure}D", 2,
+ "clause", ObjTypeString, "lextypes",
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ tspForm->prslextype));
+ list = lappend(list, new_object_object(tmp));
+
+ /* HEADLINE */
+ tmp = new_objtree_VA("HEADLINE=", 1,
+ "clause", ObjTypeString, "headline");
+ if (OidIsValid(tspForm->prsheadline))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ tspForm->prsheadline));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ ret = new_objtree_VA("CREATE TEXT SEARCH PARSER %{identity}D (%{elems:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(tspForm->prsnamespace,
+ NameStr(tspForm->prsname)),
+ "elems", ObjTypeArray, list);
+
+ ReleaseSysCache(tspTup);
+ return ret;
+}
+
+/*
+ * Deparse a CREATE TEXT SEARCH DICTIONARY statement.
+ *
+ * Verbose syntax
+ * CREATE TEXT SEARCH DICTIONARY %{identity}D (%{elems:, }s)
+ */
+static ObjTree *
+deparse_DefineStmt_TSDictionary(Oid objectId, DefineStmt *define)
+{
+ HeapTuple tsdTup;
+ ObjTree *ret;
+ ObjTree *tmp;
+ List *list = NIL;
+ Datum options;
+ bool isnull;
+ Form_pg_ts_dict tsdForm;
+
+ tsdTup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tsdTup))
+ elog(ERROR, "cache lookup failed for text search dictionary with OID %u",
+ objectId);
+ tsdForm = (Form_pg_ts_dict) GETSTRUCT(tsdTup);
+
+
+
+ /* Add the definition clause */
+ /* TEMPLATE */
+ tmp = new_objtree_VA("TEMPLATE=", 1,
+ "clause", ObjTypeString, "template");
+ append_object_object(tmp, "%{template}D",
+ new_objtree_for_qualname_id(TSTemplateRelationId,
+ tsdForm->dicttemplate));
+ list = lappend(list, new_object_object(tmp));
+
+ /*
+ * options. XXX this is a pretty useless representation, but we can't do
+ * better without more ts_cache.c cooperation ...
+ */
+ options = SysCacheGetAttr(TSDICTOID, tsdTup,
+ Anum_pg_ts_dict_dictinitoption,
+ &isnull);
+ tmp = new_objtree("");
+ if (!isnull)
+ append_string_object(tmp, "%{options}s", "options",
+ TextDatumGetCString(options));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ ret = new_objtree_VA("CREATE TEXT SEARCH DICTIONARY %{identity}D (%{elems:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(tsdForm->dictnamespace,
+ NameStr(tsdForm->dictname)),
+ "elems", ObjTypeArray, list);
+
+ ReleaseSysCache(tsdTup);
+ return ret;
+}
+
+/*
+ * Deparse a CREATE TEXT SEARCH TEMPLATE statement.
+ *
+ * Verbose syntax
+ * CREATE TEXT SEARCH TEMPLATE %{identity}D (%{elems:, }s)
+ */
+static ObjTree *
+deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define)
+{
+ HeapTuple tstTup;
+ ObjTree *ret;
+ ObjTree *tmp;
+ List *list = NIL;
+ Form_pg_ts_template tstForm;
+
+ tstTup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tstTup))
+ elog(ERROR, "cache lookup failed for text search template with OID %u",
+ objectId);
+ tstForm = (Form_pg_ts_template) GETSTRUCT(tstTup);
+
+ /* Add the definition clause */
+ /* INIT */
+ tmp = new_objtree_VA("INIT=", 1,
+ "clause", ObjTypeString, "init");
+ if (OidIsValid(tstForm->tmplinit))
+ append_object_object(tmp, "%{procedure}D",
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ tstForm->tmplinit));
+ else
+ append_not_present(tmp);
+ list = lappend(list, new_object_object(tmp));
+
+ /* LEXIZE */
+ tmp = new_objtree_VA("LEXIZE=%{procedure}D", 2,
+ "clause", ObjTypeString, "lexize",
+ "procedure", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ tstForm->tmpllexize));
+ list = lappend(list, new_object_object(tmp));
+
+ ret = new_objtree_VA("CREATE TEXT SEARCH TEMPLATE %{identity}D (%{elems:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(tstForm->tmplnamespace,
+ NameStr(tstForm->tmplname)),
+ "elems", ObjTypeArray, list);
+
+ ReleaseSysCache(tstTup);
+ return ret;
+}
+
+/*
+ * Deparse an ALTER TEXT SEARCH CONFIGURATION statement.
+ *
+ * Verbose syntax
+ * ALTER TEXT SEARCH CONFIGURATION %{identity}D ADD MAPPING
+ * FOR %{tokentype:, }I WITH %{dictionaries:, }D
+ * OR
+ * ALTER TEXT SEARCH CONFIGURATION %{identity}D DROP MAPPING %{if_exists}s
+ * FOR %{tokentype}I
+ * OR
+ * ALTER TEXT SEARCH CONFIGURATION %{identity}D ALTER MAPPING
+ * FOR %{tokentype:, }I WITH %{dictionaries:, }D
+ * OR
+ * ALTER TEXT SEARCH CONFIGURATION %{identity}D ALTER MAPPING
+ * REPLACE %{old_dictionary}D WITH %{new_dictionary}D
+ * OR
+ * ALTER TEXT SEARCH CONFIGURATION %{identity}D ALTER MAPPING
+ * FOR %{tokentype:, }I REPLACE %{old_dictionary}D WITH %{new_dictionary}D
+ */
+static ObjTree *
+deparse_AlterTSConfigurationStmt(CollectedCommand *cmd)
+{
+ AlterTSConfigurationStmt *node = (AlterTSConfigurationStmt *) cmd->parsetree;
+ ObjTree *ret;
+ ObjTree *tmp;
+ List *list = NIL;
+ ListCell *cell;
+ int i;
+
+ ret = new_objtree("ALTER TEXT SEARCH CONFIGURATION");
+
+ /* Determine the format string appropriate to each subcommand */
+ switch (node->kind)
+ {
+ case ALTER_TSCONFIG_ADD_MAPPING:
+ append_object_object(ret, "%{identity}D ADD MAPPING",
+ new_objtree_for_qualname_id(cmd->d.atscfg.address.classId,
+ cmd->d.atscfg.address.objectId));
+ break;
+
+ case ALTER_TSCONFIG_DROP_MAPPING:
+ append_object_object(ret, "%{identity}D DROP MAPPING",
+ new_objtree_for_qualname_id(cmd->d.atscfg.address.classId,
+ cmd->d.atscfg.address.objectId));
+ tmp = new_objtree("IF EXISTS");
+ append_bool_object(tmp, "present", node->missing_ok);
+ append_object_object(ret, "%{if_exists}s", tmp);
+ break;
+
+ case ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN:
+ case ALTER_TSCONFIG_REPLACE_DICT:
+ case ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN:
+ append_object_object(ret, "%{identity}D ALTER MAPPING",
+ new_objtree_for_qualname_id(cmd->d.atscfg.address.classId,
+ cmd->d.atscfg.address.objectId));
+ break;
+ }
+
+ /* Add the affected token list, for subcommands that have one */
+ if (node->kind == ALTER_TSCONFIG_ADD_MAPPING ||
+ node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN ||
+ node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN ||
+ node->kind == ALTER_TSCONFIG_DROP_MAPPING)
+ {
+ foreach(cell, node->tokentype)
+ list = lappend(list, new_string_object(strVal(lfirst(cell))));
+ append_array_object(ret, "FOR %{tokentype:, }I", list);
+ }
+
+ /* Add further subcommand-specific elements */
+ if (node->kind == ALTER_TSCONFIG_ADD_MAPPING ||
+ node->kind == ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN)
+ {
+ /* ADD MAPPING and ALTER MAPPING FOR need a list of dictionaries */
+ list = NIL;
+ for (i = 0; i < cmd->d.atscfg.ndicts; i++)
+ {
+ ObjTree *dict_obj;
+
+ dict_obj = new_objtree_for_qualname_id(TSDictionaryRelationId,
+ cmd->d.atscfg.dictIds[i]);
+ list = lappend(list,
+ new_object_object(dict_obj));
+ }
+ append_array_object(ret, "WITH %{dictionaries:, }D", list);
+ }
+ else if (node->kind == ALTER_TSCONFIG_REPLACE_DICT ||
+ node->kind == ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN)
+ {
+ /* The REPLACE forms want old and new dictionaries */
+ Assert(cmd->d.atscfg.ndicts == 2);
+ append_object_object(ret, "REPLACE %{old_dictionary}D",
+ new_objtree_for_qualname_id(TSDictionaryRelationId,
+ cmd->d.atscfg.dictIds[0]));
+ append_object_object(ret, "WITH %{new_dictionary}D",
+ new_objtree_for_qualname_id(TSDictionaryRelationId,
+ cmd->d.atscfg.dictIds[1]));
+ }
+
+ return ret;
+}
+
+/*
+ * Deparse an ALTER TEXT SEARCH DICTIONARY statement.
+ *
+ * Verbose syntax
+ * ALTER TEXT SEARCH DICTIONARY %{identity}D (%{definition:, }s)
+ */
+static ObjTree *
+deparse_AlterTSDictionaryStmt(Oid objectId, Node *parsetree)
+{
+ ObjTree *ret;
+ ObjTree *tmp;
+ Datum options;
+ List *definition = NIL;
+ bool isnull;
+ HeapTuple tsdTup;
+ Form_pg_ts_dict tsdForm;
+
+ tsdTup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tsdTup))
+ elog(ERROR, "cache lookup failed for text search dictionary with OID %u",
+ objectId);
+ tsdForm = (Form_pg_ts_dict) GETSTRUCT(tsdTup);
+
+ /*
+ * Add the definition list according to the pg_ts_dict dictinitoption
+ * column
+ */
+ options = SysCacheGetAttr(TSDICTOID, tsdTup,
+ Anum_pg_ts_dict_dictinitoption,
+ &isnull);
+ tmp = new_objtree("");
+ if (!isnull)
+ append_string_object(tmp, "%{options}s", "options",
+ TextDatumGetCString(options));
+ else
+ append_not_present(tmp);
+
+ definition = lappend(definition, new_object_object(tmp));
+
+ ret = new_objtree_VA("ALTER TEXT SEARCH DICTIONARY %{identity}D (%{definition:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(tsdForm->dictnamespace,
+ NameStr(tsdForm->dictname)),
+ "definition", ObjTypeArray, definition);
+
+ ReleaseSysCache(tsdTup);
+ return ret;
+}
+
+/*
+ * deparse_ViewStmt
+ * deparse a ViewStmt
+ *
+ * Given a view OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s
+ */
+static ObjTree *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+ ViewStmt *node = (ViewStmt *) parsetree;
+ ObjTree *ret;
+ Relation relation;
+
+ relation = relation_open(objectId, AccessShareLock);
+
+ ret = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s", 4,
+ "or_replace", ObjTypeString,
+ node->replace ? "OR REPLACE" : "",
+ "persistence", ObjTypeString,
+ get_persistence_str(relation->rd_rel->relpersistence),
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(relation->rd_rel->relnamespace,
+ RelationGetRelationName(relation)),
+ "query", ObjTypeString,
+ pg_get_viewdef_string(objectId));
+
+ relation_close(relation, AccessShareLock);
+ return ret;
+}
+
+/*
+ * Deparse CREATE Materialized View statement, it is a variant of
+ * CreateTableAsStmt
+ *
+ * Note that CREATE TABLE AS SELECT INTO can also be deparsed by
+ * deparse_CreateTableAsStmt to remove the SELECT INTO clause.
+ *
+ * Verbose syntax
+ * CREATE %{persistence}s [MATERIALIZED VIEW | TABLE] %{if_not_exists}s
+ * %{identity}D %{columns}s [%{on_commit}s] %{tablespace}s
+ * AS %{query}s %{with_no_data}s"
+ */
+static ObjTree *
+deparse_CreateTableAsStmt_vanilla(Oid objectId, Node *parsetree)
+{
+ CreateTableAsStmt *node = (CreateTableAsStmt *) parsetree;
+ Relation relation = relation_open(objectId, AccessShareLock);
+ ObjTree *ret;
+ ObjTree *tmp;
+ ObjTree *tmp2;
+ char *fmt;
+ List *list = NIL;
+ ListCell *cell;
+
+ /* Reject unsupported case right away. */
+ if (((Query *) (node->query))->commandType == CMD_UTILITY)
+ elog(ERROR, "unimplemented deparse of CREATE TABLE AS EXECUTE");
+
+ /*
+ * Note that INSERT INTO is deparsed as CREATE TABLE AS. They are
+ * functionally equivalent synonyms so there is no harm from this.
+ */
+ if (node->objtype == OBJECT_MATVIEW)
+ fmt = "CREATE %{persistence}s MATERIALIZED VIEW %{if_not_exists}s %{identity}D";
+ else
+ fmt = "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D";
+
+ ret = new_objtree_VA(fmt, 3,
+ "persistence", ObjTypeString,
+ get_persistence_str(node->into->rel->relpersistence),
+ "if_not_exists", ObjTypeString,
+ node->if_not_exists ? "IF NOT EXISTS" : "",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ objectId));
+
+ /* COLUMNS clause */
+ if (node->into->colNames)
+ {
+ foreach(cell, node->into->colNames)
+ list = lappend(list, new_string_object(strVal(lfirst(cell))));
+
+ tmp = new_objtree_VA("(%{columns:, }I)", 1,
+ "columns", ObjTypeArray, list);
+ }
+ else
+ tmp = new_objtree_VA("", 1,
+ "present", ObjTypeBool, false);
+
+ append_object_object(ret, "%{columns}s", tmp);
+
+ /* USING clause */
+ tmp = new_objtree("USING");
+ if (node->into->accessMethod)
+ append_string_object(tmp, "%{access_method}I", "access_method",
+ node->into->accessMethod);
+ else
+ {
+ append_null_object(tmp, "%{access_method}I");
+ append_not_present(tmp);
+ }
+ append_object_object(ret, "%{access_method}s", tmp);
+
+ /* WITH clause */
+ tmp = new_objtree("WITH");
+ list = NIL;
+
+ foreach(cell, node->into->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(cell);
+
+ tmp2 = deparse_DefElem(opt, false);
+ list = lappend(list, new_object_object(tmp2));
+ }
+
+ if (list)
+ append_array_object(tmp, "(%{with:, }s)", list);
+ else
+ append_not_present(tmp);
+
+ append_object_object(ret, "%{with_clause}s", tmp);
+
+ /* ON COMMIT clause. CREATE MATERIALIZED VIEW doesn't have one */
+ if (node->objtype == OBJECT_TABLE)
+ append_object_object(ret, "%{on_commit}s",
+ deparse_OnCommitClause(node->into->onCommit));
+
+ /* TABLESPACE clause */
+ tmp = new_objtree("TABLESPACE %{tablespace}I");
+ if (node->into->tableSpaceName)
+ append_string_object(tmp, "%{tablespace}s", "tablespace",
+ node->into->tableSpaceName);
+ else
+ {
+ append_null_object(tmp, "%{tablespace}I");
+ append_not_present(tmp);
+ }
+ append_object_object(ret, "%{tablespace}s", tmp);
+
+ /* Add the query */
+ Assert(IsA(node->query, Query));
+ append_string_object(ret, "AS %{query}s", "query",
+ pg_get_querydef((Query *) node->query, false));
+
+ /* Add a WITH NO DATA clause */
+ tmp = new_objtree_VA("WITH NO DATA", 1,
+ "present", ObjTypeBool,
+ node->into->skipData ? true : false);
+ append_object_object(ret, "%{with_no_data}s", tmp);
+
+ relation_close(relation, AccessShareLock);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateTrigStmt (CREATE TRIGGER)
+ *
+ * Given a trigger OID and the parse tree that created it, return an ObjTree
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE %{or_replace}s %{constraint}s TRIGGER %{name}I %{time}s
+ * %{events: OR }s ON %{relation}D %{from_table}s %{constraint_attrs: }s
+ * %{referencing: }s FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE
+ * %{function}s
+ */
+static ObjTree *
+deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
+{
+ CreateTrigStmt *node = (CreateTrigStmt *) parsetree;
+ Relation pg_trigger;
+ HeapTuple trigTup;
+ Form_pg_trigger trigForm;
+ ObjTree *ret;
+ ObjTree *tmp_obj;
+ int tgnargs;
+ List *list = NIL;
+ List *events;
+ char *trigtiming;
+ char *tgoldtable;
+ char *tgnewtable;
+ Datum value;
+ bool isnull;
+
+ pg_trigger = table_open(TriggerRelationId, AccessShareLock);
+
+ trigTup = get_catalog_object_by_oid(pg_trigger, Anum_pg_trigger_oid, objectId);
+ trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+ trigtiming = node->timing == TRIGGER_TYPE_BEFORE ? "BEFORE" :
+ node->timing == TRIGGER_TYPE_AFTER ? "AFTER" :
+ node->timing == TRIGGER_TYPE_INSTEAD ? "INSTEAD OF" :
+ NULL;
+ if (!trigtiming)
+ elog(ERROR, "unrecognized trigger timing type %d", node->timing);
+
+ ret = new_objtree_VA("CREATE %{or_replace}s %{constraint}s TRIGGER %{name}I %{time}s", 4,
+ "or_replace", ObjTypeString, node->replace ? "OR REPLACE" : "",
+ "constraint", ObjTypeString, node->isconstraint ? "CONSTRAINT" : "",
+ "name", ObjTypeString, node->trigname,
+ "time", ObjTypeString, trigtiming);
+
+ /*
+ * Decode the events that the trigger fires for. The output is a list; in
+ * most cases it will just be a string with the event name, but when
+ * there's an UPDATE with a list of columns, we return a JSON object.
+ */
+ events = NIL;
+ if (node->events & TRIGGER_TYPE_INSERT)
+ events = lappend(events, new_string_object("INSERT"));
+ if (node->events & TRIGGER_TYPE_DELETE)
+ events = lappend(events, new_string_object("DELETE"));
+ if (node->events & TRIGGER_TYPE_TRUNCATE)
+ events = lappend(events, new_string_object("TRUNCATE"));
+ if (node->events & TRIGGER_TYPE_UPDATE)
+ {
+ if (node->columns == NIL)
+ {
+ events = lappend(events, new_string_object("UPDATE"));
+ }
+ else
+ {
+ ObjTree *update;
+ ListCell *cell;
+ List *cols = NIL;
+
+ /*
+ * Currently only UPDATE OF can be objects in the output JSON, but
+ * we add a "kind" element so that user code can distinguish
+ * possible future new event types.
+ */
+ update = new_objtree_VA("UPDATE OF", 1,
+ "kind", ObjTypeString, "update_of");
+
+ foreach(cell, node->columns)
+ {
+ char *colname = strVal(lfirst(cell));
+
+ cols = lappend(cols, new_string_object(colname));
+ }
+
+ append_array_object(update, "%{columns:, }I", cols);
+
+ events = lappend(events, new_object_object(update));
+ }
+ }
+ append_array_object(ret, "%{events: OR }s", events);
+
+ tmp_obj = new_objtree_for_qualname_id(RelationRelationId,
+ trigForm->tgrelid);
+ append_object_object(ret, "ON %{relation}D", tmp_obj);
+
+ tmp_obj = new_objtree("FROM");
+ if (trigForm->tgconstrrelid)
+ {
+ ObjTree *rel;
+
+ rel = new_objtree_for_qualname_id(RelationRelationId,
+ trigForm->tgconstrrelid);
+ append_object_object(tmp_obj, "%{relation}D", rel);
+ }
+ else
+ append_not_present(tmp_obj);
+ append_object_object(ret, "%{from_table}s", tmp_obj);
+
+ if (node->isconstraint)
+ {
+ if (!node->deferrable)
+ list = lappend(list, new_string_object("NOT"));
+ list = lappend(list, new_string_object("DEFERRABLE INITIALLY"));
+ if (node->initdeferred)
+ list = lappend(list, new_string_object("DEFERRED"));
+ else
+ list = lappend(list, new_string_object("IMMEDIATE"));
+ }
+ append_array_object(ret, "%{constraint_attrs: }s", list);
+
+ list = NIL;
+ value = fastgetattr(trigTup, Anum_pg_trigger_tgoldtable,
+ RelationGetDescr(pg_trigger), &isnull);
+ if (!isnull)
+ tgoldtable = NameStr(*DatumGetName(value));
+ else
+ tgoldtable = NULL;
+ value = fastgetattr(trigTup, Anum_pg_trigger_tgnewtable,
+ RelationGetDescr(pg_trigger), &isnull);
+ if (!isnull)
+ tgnewtable = NameStr(*DatumGetName(value));
+ else
+ tgnewtable = NULL;
+ if (tgoldtable != NULL || tgnewtable != NULL)
+ {
+ list = lappend(list, new_string_object("REFERENCING"));
+
+ if (tgoldtable != NULL)
+ {
+ list = lappend(list, new_string_object("OLD TABLE AS"));
+ list = lappend(list, new_string_object(tgoldtable));
+ }
+ if (tgnewtable != NULL)
+ {
+ list = lappend(list, new_string_object("NEW TABLE AS"));
+ list = lappend(list, new_string_object(tgnewtable));
+ }
+ }
+ append_array_object(ret, "%{referencing: }s", list);
+
+ append_string_object(ret, "FOR EACH %{for_each}s", "for_each",
+ node->row ? "ROW" : "STATEMENT");
+
+ tmp_obj = new_objtree("WHEN");
+ if (node->whenClause)
+ {
+ Node *whenClause;
+
+ value = fastgetattr(trigTup, Anum_pg_trigger_tgqual,
+ RelationGetDescr(pg_trigger), &isnull);
+ if (isnull)
+ elog(ERROR, "null tgqual for trigger \"%s\"",
+ NameStr(trigForm->tgname));
+
+ whenClause = stringToNode(TextDatumGetCString(value));
+ append_string_object(tmp_obj, "(%{clause}s)", "clause",
+ pg_get_trigger_whenclause(trigForm,
+ whenClause,
+ false));
+ }
+ else
+ append_not_present(tmp_obj);
+ append_object_object(ret, "%{when}s", tmp_obj);
+
+ tmp_obj = new_objtree_VA("%{funcname}D", 1,
+ "funcname", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ trigForm->tgfoid));
+ list = NIL;
+ tgnargs = trigForm->tgnargs;
+ if (tgnargs > 0)
+ {
+ char *p;
+ int i;
+
+ value = fastgetattr(trigTup, Anum_pg_trigger_tgargs,
+ RelationGetDescr(pg_trigger), &isnull);
+ if (isnull)
+ elog(ERROR, "null tgargs for trigger \"%s\"",
+ NameStr(trigForm->tgname));
+ p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
+ for (i = 0; i < tgnargs; i++)
+ {
+ list = lappend(list, new_string_object(p));
+ /* advance p to next string embedded in tgargs */
+ while (*p)
+ p++;
+ p++;
+ }
+ }
+
+ append_format_string(tmp_obj, "(");
+ append_array_object(tmp_obj, "%{args:, }L", list); /* might be NIL */
+ append_format_string(tmp_obj, ")");
+
+ append_object_object(ret, "EXECUTE FUNCTION %{function}s", tmp_obj);
+
+ table_close(pg_trigger, AccessShareLock);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateUserMappingStmt (CREATE USER MAPPING)
+ *
+ * Given a User Mapping OID and the parse tree that created it,
+ * return an ObjTree representing the CREATE USER MAPPING command.
+ *
+ * Verbose syntax
+ * CREATE USER MAPPING FOR %{role}R SERVER %{server}I
+ */
+static ObjTree *
+deparse_CreateUserMappingStmt(Oid objectId, Node *parsetree)
+{
+ CreateUserMappingStmt *node = (CreateUserMappingStmt *) parsetree;
+ ObjTree *ret;
+ Relation rel;
+ HeapTuple tp;
+ Form_pg_user_mapping form;
+ ForeignServer *server;
+
+ rel = table_open(UserMappingRelationId, RowExclusiveLock);
+
+ /*
+ * Lookup object in the catalog, so we don't have to deal with
+ * current_user and such.
+ */
+ tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for user mapping with OID %u",
+ objectId);
+
+ form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+ server = GetForeignServer(form->umserver);
+
+ ret = new_objtree_VA("CREATE USER MAPPING FOR %{role}R SERVER %{server}I %{generic_options}s", 3,
+ "role", ObjTypeObject,
+ new_objtree_for_role_id(form->umuser),
+ "server", ObjTypeString, server->servername,
+ "generic_options", ObjTypeObject,
+ deparse_FdwOptions(node->options, NULL, false));
+
+ ReleaseSysCache(tp);
+ table_close(rel, RowExclusiveLock);
+ return ret;
+}
+
+/*
+ * deparse_AlterUserMapping
+ *
+ * Given a User Mapping OID and the parse tree that created it, return an
+ * ObjTree representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER USER MAPPING FOR %{role}R SERVER %{server}I
+ */
+static ObjTree *
+deparse_AlterUserMappingStmt(Oid objectId, Node *parsetree)
+{
+ AlterUserMappingStmt *node = (AlterUserMappingStmt *) parsetree;
+ ObjTree *ret;
+ Relation rel;
+ HeapTuple tp;
+ Form_pg_user_mapping form;
+ ForeignServer *server;
+
+ rel = table_open(UserMappingRelationId, RowExclusiveLock);
+
+ /*
+ * Lookup object in the catalog, so we don't have to deal with
+ * current_user and such.
+ */
+ tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for user mapping with OID %u",
+ objectId);
+
+ form = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+ server = GetForeignServer(form->umserver);
+
+ ret = new_objtree_VA("ALTER USER MAPPING FOR %{role}R SERVER %{server}I %{generic_options}s", 3,
+ "role", ObjTypeObject,
+ new_objtree_for_role_id(form->umuser),
+ "server", ObjTypeString, server->servername,
+ "generic_options", ObjTypeObject,
+ deparse_FdwOptions(node->options, NULL, false));
+
+ ReleaseSysCache(tp);
+ table_close(rel, RowExclusiveLock);
+ return ret;
+}
+
+/*
+ * Deparse an AlterStatsStmt (ALTER STATISTICS)
+ *
+ * Given a alter statistics OID and the parse tree that created it, return an
+ * ObjTree representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER STATISTICS %{identity}D SET STATISTICS %{target}n
+ */
+static ObjTree *
+deparse_AlterStatsStmt(Oid objectId, Node *parsetree)
+{
+ AlterStatsStmt *node = (AlterStatsStmt *) parsetree;
+ ObjTree *ret;
+ HeapTuple tp;
+ Form_pg_statistic_ext statform;
+
+ if (!node->stxstattarget)
+ return NULL;
+
+ /* Lookup object in the catalog */
+ tp = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for statistic with OID %u", objectId);
+
+ statform = (Form_pg_statistic_ext) GETSTRUCT(tp);
+ ret = new_objtree_VA("ALTER STATISTICS %{identity}D SET STATISTICS %{target}n", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(statform->stxnamespace,
+ NameStr(statform->stxname)),
+ "target", ObjTypeInteger, node->stxstattarget);
+
+ ReleaseSysCache(tp);
+ return ret;
+}
+
+/*
+ * Deparse a RefreshMatViewStmt (REFRESH MATERIALIZED VIEW)
+ *
+ * Given a materialized view OID and the parse tree that created it, return an
+ * ObjTree representing the refresh command.
+ *
+ * Verbose syntax
+ * REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D %{with_no_data}s
+ */
+static ObjTree *
+deparse_RefreshMatViewStmt(Oid objectId, Node *parsetree)
+{
+ RefreshMatViewStmt *node = (RefreshMatViewStmt *) parsetree;
+ ObjTree *ret;
+ ObjTree *tmp;
+
+ ret = new_objtree_VA("REFRESH MATERIALIZED VIEW %{concurrently}s %{identity}D", 2,
+ "concurrently", ObjTypeString,
+ node->concurrent ? "CONCURRENTLY" : "",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ objectId));
+
+ /* Add a WITH NO DATA clause */
+ tmp = new_objtree_VA("WITH NO DATA", 1,
+ "present", ObjTypeBool,
+ node->skipData ? true : false);
+ append_object_object(ret, "%{with_no_data}s", tmp);
+
+ return ret;
+}
+
+/*
+ * Deparse a RenameStmt.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I
+ * OR
+ * ALTER POLICY %{if_exists}s %{policyname}I ON %{identity}D RENAME TO %{newname}I
+ * OR
+ * ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I
+ * OR
+ * ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I
+ * OR
+ * ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I TO %{newname}I %{cascade}s
+ * OR
+ * ALTER %{objtype}s %{if_exists}s %{only}s %{identity}D RENAME COLUMN %{colname}I TO %{newname}I %{cascade}s
+ * OR
+ * ALTER %{objtype}s %{identity}s RENAME TO %{newname}I
+ * OR
+ * ALTER %{objtype}s %{identity}D USING %{index_method}s RENAME TO %{newname}I
+ * OR
+ * ALTER SCHEMA %{identity}I RENAME TO %{newname}I
+ * OR
+ * ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I
+ * OR
+ * ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I
+ * OR
+ * ALTER %{objtype}s %{identity}D RENAME TO %{newname}I
+ */
+static ObjTree *
+deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+{
+ RenameStmt *node = (RenameStmt *) parsetree;
+ ObjTree *ret;
+ Relation relation;
+ Oid schemaId;
+
+ /*
+ * 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:
+ case OBJECT_INDEX:
+ case OBJECT_SEQUENCE:
+ case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
+ case OBJECT_FOREIGN_TABLE:
+ relation = relation_open(address.objectId, AccessShareLock);
+ schemaId = RelationGetNamespace(relation);
+ ret = new_objtree_VA("ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I", 4,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->renameType, false),
+ "if_exists", ObjTypeString,
+ node->missing_ok ? "IF EXISTS" : "",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(schemaId,
+ node->relation->relname),
+ "newname", ObjTypeString,
+ node->newname);
+ relation_close(relation, AccessShareLock);
+ break;
+
+ case OBJECT_POLICY:
+ {
+ HeapTuple polTup;
+ Form_pg_policy polForm;
+ Relation pg_policy;
+ ScanKeyData key;
+ SysScanDesc scan;
+
+ pg_policy = relation_open(PolicyRelationId, AccessShareLock);
+ ScanKeyInit(&key, Anum_pg_policy_oid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(address.objectId));
+ scan = systable_beginscan(pg_policy, PolicyOidIndexId, true,
+ NULL, 1, &key);
+ polTup = systable_getnext(scan);
+ if (!HeapTupleIsValid(polTup))
+ elog(ERROR, "cache lookup failed for policy with OID %u",
+ address.objectId);
+ polForm = (Form_pg_policy) GETSTRUCT(polTup);
+
+ ret = new_objtree_VA("ALTER POLICY %{if_exists}s %{policyname}I ON %{identity}D RENAME TO %{newname}I", 4,
+ "if_exists", ObjTypeString,
+ node->missing_ok ? "IF EXISTS" : "",
+ "policyname", ObjTypeString,
+ node->subname,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ polForm->polrelid),
+ "newname", ObjTypeString,
+ node->newname);
+ systable_endscan(scan);
+ relation_close(pg_policy, AccessShareLock);
+ }
+ break;
+
+ case OBJECT_DOMCONSTRAINT:
+ {
+ HeapTuple domtup;
+ HeapTuple consttup;
+ Form_pg_constraint constform;
+ Form_pg_type domform;
+
+ /* Get domain id from the constraint */
+ consttup = SearchSysCache1(CONSTROID,
+ ObjectIdGetDatum(address.objectId));
+ if (!HeapTupleIsValid(consttup))
+ elog(ERROR, "cache lookup failed for constraint with OID %u",
+ address.objectId);
+ constform = (Form_pg_constraint) GETSTRUCT(consttup);
+
+ domtup = SearchSysCache1(TYPEOID,
+ ObjectIdGetDatum(constform->contypid));
+ if (!HeapTupleIsValid(domtup))
+ elog(ERROR, "cache lookup failed for domain with OID %u",
+ constform->contypid);
+ ReleaseSysCache(consttup);
+
+ domform = (Form_pg_type) GETSTRUCT(domtup);
+ ret = new_objtree_VA("ALTER DOMAIN %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I", 3,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(domform->typnamespace,
+ NameStr(domform->typname)),
+ "oldname", ObjTypeString, node->subname,
+ "newname", ObjTypeString, node->newname);
+ ReleaseSysCache(domtup);
+ }
+ 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);
+
+ ret = new_objtree_VA("ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I", 4,
+ "only", ObjTypeString,
+ node->relation->inh ? "" : "ONLY",
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ constform->conrelid),
+ "oldname", ObjTypeString, node->subname,
+ "newname", ObjTypeString, node->newname);
+ ReleaseSysCache(constrtup);
+ }
+ break;
+
+ case OBJECT_ATTRIBUTE:
+ case OBJECT_COLUMN:
+ relation = relation_open(address.objectId, AccessShareLock);
+ schemaId = RelationGetNamespace(relation);
+
+ if (node->renameType == OBJECT_ATTRIBUTE)
+ ret = new_objtree_VA("ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(schemaId,
+ node->relation->relname),
+ "colname", ObjTypeString, node->subname);
+ else
+ {
+ ret = new_objtree_VA("ALTER %{objtype}s", 1,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->relationType, false));
+
+ /* Composite types do not support IF EXISTS */
+ if (node->renameType == OBJECT_COLUMN)
+ append_string_object(ret, "%{if_exists}s",
+ "if_exists",
+ node->missing_ok ? "IF EXISTS" : "");
+ if (!node->relation->inh)
+ append_string_object(ret, "%{only}s",
+ "only",
+ "ONLY");
+ append_object_object(ret, "%{identity}D",
+ new_objtree_for_qualname(schemaId,
+ node->relation->relname));
+ append_string_object(ret, "RENAME COLUMN %{colname}I",
+ "colname", node->subname);
+ }
+
+ append_string_object(ret, "TO %{newname}I", "newname",
+ node->newname);
+
+ if (node->renameType == OBJECT_ATTRIBUTE)
+ append_object_object(ret, "%{cascade}s",
+ new_objtree_VA("CASCADE", 1,
+ "present", ObjTypeBool,
+ node->behavior == DROP_CASCADE));
+
+ relation_close(relation, AccessShareLock);
+ break;
+
+ case OBJECT_AGGREGATE:
+ case OBJECT_FUNCTION:
+ case OBJECT_ROUTINE:
+ {
+ char *ident;
+ HeapTuple proctup;
+ Form_pg_proc procform;
+ List *identity;
+
+ Assert(IsA(node->object, ObjectWithArgs));
+ identity = ((ObjectWithArgs *) node->object)->objname;
+
+ proctup = SearchSysCache1(PROCOID,
+ ObjectIdGetDatum(address.objectId));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for procedure with OID %u",
+ address.objectId);
+ procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+ /* XXX does this work for ordered-set aggregates? */
+ ident = psprintf("%s%s",
+ quote_qualified_identifier(get_namespace_name(procform->pronamespace),
+ strVal(llast(identity))),
+ format_procedure_args(address.objectId, true));
+
+ ret = new_objtree_VA("ALTER %{objtype}s %{identity}s RENAME TO %{newname}I", 3,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->renameType, false),
+ "identity", ObjTypeString, ident,
+ "newname", ObjTypeString, node->newname);
+
+ ReleaseSysCache(proctup);
+ }
+ break;
+
+ case OBJECT_OPCLASS:
+ {
+ HeapTuple opcTup;
+ Form_pg_opclass opcForm;
+ List *oldnames;
+ char *schemaname;
+ char *opcname;
+
+ opcTup = SearchSysCache1(CLAOID, ObjectIdGetDatum(address.objectId));
+ if (!HeapTupleIsValid(opcTup))
+ elog(ERROR, "cache lookup failed for opclass with OID %u",
+ address.objectId);
+
+ opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+
+ oldnames = list_copy_tail((List *) node->object, 1);
+
+ /* Deconstruct the name list */
+ DeconstructQualifiedName(oldnames, &schemaname, &opcname);
+
+ ret = new_objtree_VA("ALTER %{objtype}s %{identity}D USING %{index_method}s RENAME TO %{newname}I", 4,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->renameType, false),
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(opcForm->opcnamespace,
+ opcname),
+ "index_method", ObjTypeString,
+ get_am_name(opcForm->opcmethod),
+ "newname", ObjTypeString, node->newname);
+
+ ReleaseSysCache(opcTup);
+ }
+ break;
+
+ case OBJECT_OPFAMILY:
+ {
+ HeapTuple opfTup;
+ HeapTuple amTup;
+ Form_pg_opfamily opfForm;
+ Form_pg_am amForm;
+ List *oldnames;
+ char *schemaname;
+ char *opfname;
+
+ opfTup = SearchSysCache1(OPFAMILYOID, address.objectId);
+ if (!HeapTupleIsValid(opfTup))
+ elog(ERROR, "cache lookup failed for operator family with OID %u",
+ address.objectId);
+ opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+ amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
+ if (!HeapTupleIsValid(amTup))
+ elog(ERROR, "cache lookup failed for access method with OID %u",
+ opfForm->opfmethod);
+
+ amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+ oldnames = list_copy_tail((List *) node->object, 1);
+
+ /* Deconstruct the name list */
+ DeconstructQualifiedName(oldnames, &schemaname, &opfname);
+
+ ret = new_objtree_VA("ALTER %{objtype}s %{identity}D USING %{index_method}s RENAME TO %{newname}I", 4,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->renameType, false),
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(opfForm->opfnamespace,
+ opfname),
+ "index_method", ObjTypeString,
+ NameStr(amForm->amname),
+ "newname", ObjTypeString, node->newname);
+
+ ReleaseSysCache(amTup);
+ ReleaseSysCache(opfTup);
+ }
+ break;
+
+ case OBJECT_SCHEMA:
+ ret = new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I", 2,
+ "identity", ObjTypeString, node->subname,
+ "newname", ObjTypeString, node->newname);
+ break;
+
+ case OBJECT_FDW:
+ case OBJECT_LANGUAGE:
+ case OBJECT_FOREIGN_SERVER:
+ case OBJECT_PUBLICATION:
+ ret = new_objtree_VA("ALTER %{objtype}s %{identity}s RENAME TO %{newname}I", 3,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->renameType, false),
+ "identity", ObjTypeString,
+ strVal(castNode(String, node->object)),
+ "newname", ObjTypeString, node->newname);
+ break;
+
+ case OBJECT_RULE:
+ {
+ HeapTuple rewrTup;
+ Form_pg_rewrite rewrForm;
+ Relation pg_rewrite;
+
+ pg_rewrite = relation_open(RewriteRelationId, AccessShareLock);
+ rewrTup = get_catalog_object_by_oid(pg_rewrite, Anum_pg_rewrite_oid, address.objectId);
+ rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup);
+
+ ret = new_objtree_VA("ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I", 3,
+ "rulename", ObjTypeString, node->subname,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ rewrForm->ev_class),
+ "newname", ObjTypeString, node->newname);
+ relation_close(pg_rewrite, AccessShareLock);
+ }
+ break;
+
+ case OBJECT_TRIGGER:
+ {
+ HeapTuple trigTup;
+ Form_pg_trigger trigForm;
+ Relation pg_trigger;
+
+ pg_trigger = relation_open(TriggerRelationId, AccessShareLock);
+ trigTup = get_catalog_object_by_oid(pg_trigger, get_object_attnum_oid(address.classId), address.objectId);
+ trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+ ret = new_objtree_VA("ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I", 3,
+ "triggername", ObjTypeString, node->subname,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ trigForm->tgrelid),
+ "newname", ObjTypeString, node->newname);
+
+ relation_close(pg_trigger, AccessShareLock);
+ }
+ break;
+
+ case OBJECT_COLLATION:
+ case OBJECT_STATISTIC_EXT:
+ case OBJECT_TYPE:
+ case OBJECT_CONVERSION:
+ case OBJECT_DOMAIN:
+ case OBJECT_TSDICTIONARY:
+ case OBJECT_TSPARSER:
+ case OBJECT_TSTEMPLATE:
+ case OBJECT_TSCONFIGURATION:
+ {
+ HeapTuple objTup;
+ Relation catalog;
+ Datum objnsp;
+ bool isnull;
+ AttrNumber Anum_namespace;
+ List *identity = castNode(List, node->object);
+
+ /* Obtain object tuple */
+ catalog = relation_open(address.classId, AccessShareLock);
+ objTup = get_catalog_object_by_oid(catalog, get_object_attnum_oid(address.classId), address.objectId);
+
+ /* Obtain namespace */
+ Anum_namespace = get_object_attnum_namespace(address.classId);
+ objnsp = heap_getattr(objTup, Anum_namespace,
+ RelationGetDescr(catalog), &isnull);
+ if (isnull)
+ elog(ERROR, "invalid NULL namespace");
+
+ ret = new_objtree_VA("ALTER %{objtype}s %{identity}D RENAME TO %{newname}I", 3,
+ "objtype", ObjTypeString,
+ stringify_objtype(node->renameType, false),
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(DatumGetObjectId(objnsp),
+ strVal(llast(identity))),
+ "newname", ObjTypeString, node->newname);
+ relation_close(catalog, AccessShareLock);
+ }
+ break;
+
+ default:
+ elog(ERROR, "unsupported object type %d", node->renameType);
+ }
+
+ return ret;
+}
+
+/*
+ * Deparse a AlterObjectDependsStmt (ALTER ... DEPENDS ON ...).
+ *
+ * Verbose syntax
+ * ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I
+ */
+static ObjTree *
+deparse_AlterDependStmt(Oid objectId, Node *parsetree)
+{
+ AlterObjectDependsStmt *node = (AlterObjectDependsStmt *) parsetree;
+ ObjTree *ret = NULL;
+
+ if (node->objectType == OBJECT_INDEX)
+ {
+ ObjTree *qualified;
+ Relation relation = relation_open(objectId, AccessShareLock);
+
+ qualified = new_objtree_for_qualname(relation->rd_rel->relnamespace,
+ node->relation->relname);
+ relation_close(relation, AccessShareLock);
+
+ ret = new_objtree_VA("ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I", 3,
+ "identity", ObjTypeObject, qualified,
+ "no", ObjTypeString,
+ node->remove ? "NO" : "",
+ "newname", ObjTypeString, strVal(node->extname));
+ }
+ else
+ elog(LOG, "unrecognized node type in deparse command: %d",
+ (int) nodeTag(parsetree));
+
+ return ret;
+}
+
+/*
+ * Deparse a RuleStmt (CREATE RULE).
+ *
+ * Given a rule OID and the parse tree that created it, return an ObjTree
+ * representing the rule command.
+ *
+ * Verbose syntax
+ * CREATE RULE %{or_replace}s %{identity}I AS ON %{event}s TO %{table}D
+ * %{where_clause}s DO %{instead}s %{actions:, }s
+ */
+static ObjTree *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+ RuleStmt *node = (RuleStmt *) parsetree;
+ ObjTree *ret;
+ ObjTree *tmp;
+ Relation pg_rewrite;
+ Form_pg_rewrite rewrForm;
+ HeapTuple rewrTup;
+ SysScanDesc scan;
+ ScanKeyData key;
+ Datum ev_qual;
+ Datum ev_actions;
+ bool isnull;
+ char *qual;
+ List *actions;
+ List *list = NIL;
+ ListCell *cell;
+ char *event_str;
+
+ pg_rewrite = table_open(RewriteRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_rewrite_oid,
+ BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(objectId));
+
+ scan = systable_beginscan(pg_rewrite, RewriteOidIndexId, true,
+ NULL, 1, &key);
+ rewrTup = systable_getnext(scan);
+ if (!HeapTupleIsValid(rewrTup))
+ elog(ERROR, "cache lookup failed for rewrite rule with OID %u",
+ objectId);
+
+ rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup);
+
+ event_str = node->event == CMD_SELECT ? "SELECT" :
+ node->event == CMD_UPDATE ? "UPDATE" :
+ node->event == CMD_DELETE ? "DELETE" :
+ node->event == CMD_INSERT ? "INSERT" : NULL;
+ Assert(event_str != NULL);
+
+ ev_qual = heap_getattr(rewrTup, Anum_pg_rewrite_ev_qual,
+ RelationGetDescr(pg_rewrite), &isnull);
+ ev_actions = heap_getattr(rewrTup, Anum_pg_rewrite_ev_action,
+ RelationGetDescr(pg_rewrite), &isnull);
+
+ pg_get_ruledef_detailed(ev_qual, ev_actions, &qual, &actions);
+
+ tmp = new_objtree("");
+ if (qual)
+ append_string_object(tmp, "WHERE %{clause}s", "clause", qual);
+ else
+ {
+ append_null_object(tmp, "WHERE %{clause}s");
+ append_not_present(tmp);
+ }
+
+ if (actions == NIL)
+ list = lappend(list, new_string_object("NOTHING"));
+ else
+ {
+ foreach(cell, actions)
+ list = lappend(list, new_string_object(lfirst(cell)));
+ }
+
+ ret = new_objtree_VA("CREATE RULE %{or_replace}s %{identity}I AS ON %{event}s TO %{table}D %{where_clause}s DO %{instead}s", 6,
+ "or_replace", ObjTypeString,
+ node->replace ? "OR REPLACE" : "",
+ "identity", ObjTypeString, node->rulename,
+ "event", ObjTypeString, event_str,
+ "table", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId,
+ rewrForm->ev_class),
+ "where_clause", ObjTypeObject, tmp,
+ "instead", ObjTypeString,
+ node->instead ? "INSTEAD" : "ALSO");
+
+ if (list_length(list) > 1)
+ append_array_object(ret, "(%{actions:; }s)", list);
+ else
+ append_array_object(ret, "%{actions:; }s", list);
+
+ systable_endscan(scan);
+ table_close(pg_rewrite, AccessShareLock);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateTransformStmt (CREATE TRANSFORM).
+ *
+ * Given a transform OID and the parse tree that created it, return an ObjTree
+ * representing the CREATE TRANSFORM command.
+ *
+ * Verbose syntax
+ * CREATE %{or_replace}s TRANSFORM FOR %{typename}D LANGUAGE %{language}I
+ * ( FROM SQL WITH FUNCTION %{signature}s, TO SQL WITH FUNCTION
+ * %{signature_tof}s )
+ */
+static ObjTree *
+deparse_CreateTransformStmt(Oid objectId, Node *parsetree)
+{
+ CreateTransformStmt *node = (CreateTransformStmt *) parsetree;
+ ObjTree *ret;
+ ObjTree *signature;
+ HeapTuple trfTup;
+ HeapTuple langTup;
+ HeapTuple procTup;
+ Form_pg_transform trfForm;
+ Form_pg_language langForm;
+ Form_pg_proc procForm;
+ int i;
+
+ /* Get the pg_transform tuple */
+ trfTup = SearchSysCache1(TRFOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(trfTup))
+ elog(ERROR, "cache lookup failed for transform with OID %u",
+ objectId);
+ trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
+
+ /* Get the corresponding pg_language tuple */
+ langTup = SearchSysCache1(LANGOID, trfForm->trflang);
+ if (!HeapTupleIsValid(langTup))
+ elog(ERROR, "cache lookup failed for language with OID %u",
+ trfForm->trflang);
+ langForm = (Form_pg_language) GETSTRUCT(langTup);
+
+ ret = new_objtree_VA("CREATE %{or_replace}s TRANSFORM FOR %{typename}D LANGUAGE %{language}I", 3,
+ "or_replace", ObjTypeString,
+ node->replace ? "OR REPLACE" : "",
+ "typename", ObjTypeObject,
+ new_objtree_for_qualname_id(TypeRelationId,
+ trfForm->trftype),
+ "language", ObjTypeString,
+ NameStr(langForm->lanname));
+
+ /* Deparse the transform_element_list */
+ if (OidIsValid(trfForm->trffromsql))
+ {
+ List *params = NIL;
+
+ /* Get the pg_proc tuple for the FROM FUNCTION */
+ procTup = SearchSysCache1(PROCOID, trfForm->trffromsql);
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function with OID %u",
+ trfForm->trffromsql);
+ procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /*
+ * CREATE TRANSFORM does not change function signature so we can use
+ * catalog to get input type Oids.
+ */
+ for (i = 0; i < procForm->pronargs; i++)
+ {
+ ObjTree *param_obj;
+
+ param_obj = new_objtree_VA("%{type}T", 1,
+ "type", ObjTypeObject,
+ new_objtree_for_type(procForm->proargtypes.values[i], -1));
+ params = lappend(params, new_object_object(param_obj));
+ }
+
+ signature = new_objtree_VA("%{identity}D (%{arguments:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(procForm->pronamespace,
+ NameStr(procForm->proname)),
+ "arguments", ObjTypeArray, params);
+
+ append_object_object(ret, "(FROM SQL WITH FUNCTION %{signature}s",
+ signature);
+ ReleaseSysCache(procTup);
+ }
+ if (OidIsValid(trfForm->trftosql))
+ {
+ List *params = NIL;
+
+ /* Append a ',' if trffromsql is present, else append '(' */
+ append_format_string(ret, OidIsValid(trfForm->trffromsql) ? "," : "(");
+
+ /* Get the pg_proc tuple for the TO FUNCTION */
+ procTup = SearchSysCache1(PROCOID, trfForm->trftosql);
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function with OID %u",
+ trfForm->trftosql);
+ procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /*
+ * CREATE TRANSFORM does not change function signature so we can use
+ * catalog to get input type Oids.
+ */
+ for (i = 0; i < procForm->pronargs; i++)
+ {
+ ObjTree *param_obj = new_objtree("");
+
+ param_obj = new_objtree_VA("%{type}T", 1,
+ "type", ObjTypeObject,
+ new_objtree_for_type(procForm->proargtypes.values[i], -1));
+ params = lappend(params, new_object_object(param_obj));
+ }
+
+ signature = new_objtree_VA("%{identity}D (%{arguments:, }s)", 2,
+ "identity", ObjTypeObject,
+ new_objtree_for_qualname(procForm->pronamespace,
+ NameStr(procForm->proname)),
+ "arguments", ObjTypeArray, params);
+
+ append_object_object(ret, "TO SQL WITH FUNCTION %{signature_tof}s",
+ signature);
+ ReleaseSysCache(procTup);
+ }
+
+ append_format_string(ret, ")");
+
+ ReleaseSysCache(langTup);
+ ReleaseSysCache(trfTup);
+ return ret;
+}
+
+/*
+ * Deparse a CommentStmt when it pertains to a constraint.
+ *
+ * Verbose syntax
+ * COMMENT ON CONSTRAINT %{identity}s ON [DOMAIN] %{parentobj}s IS %{comment}s
+ */
+static ObjTree *
+deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
+{
+ CommentStmt *node = (CommentStmt *) parsetree;
+ ObjTree *ret;
+ HeapTuple constrTup;
+ Form_pg_constraint constrForm;
+ ObjectAddress addr;
+
+ Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
+
+ constrTup = SearchSysCache1(CONSTROID, objectId);
+ if (!HeapTupleIsValid(constrTup))
+ elog(ERROR, "cache lookup failed for constraint with OID %u", objectId);
+ constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
+
+ if (OidIsValid(constrForm->conrelid))
+ ObjectAddressSet(addr, RelationRelationId, constrForm->conrelid);
+ else
+ ObjectAddressSet(addr, TypeRelationId, constrForm->contypid);
+
+ ret = new_objtree_VA("COMMENT ON CONSTRAINT %{identity}s ON %{domain}s %{parentobj}s", 3,
+ "identity", ObjTypeString, pstrdup(NameStr(constrForm->conname)),
+ "domain", ObjTypeString,
+ (node->objtype == OBJECT_DOMCONSTRAINT) ? "DOMAIN" : "",
+ "parentobj", ObjTypeString,
+ getObjectIdentity(&addr, false));
+
+ /* Add the comment clause */
+ append_literal_or_null(ret, "IS %{comment}s", node->comment);
+
+ ReleaseSysCache(constrTup);
+ return ret;
+}
+
+/*
+ * Deparse an CommentStmt (COMMENT ON ...).
+ *
+ * Given the object address and the parse tree that created it, return an
+ * ObjTree representing the comment command.
+ *
+ * Verbose syntax
+ * COMMENT ON %{objtype}s %{identity}s IS %{comment}s
+ */
+static ObjTree *
+deparse_CommentStmt(ObjectAddress address, Node *parsetree)
+{
+ CommentStmt *node = (CommentStmt *) parsetree;
+ ObjTree *ret;
+ char *identity;
+
+ /* Comment on subscription is not supported */
+ if (node->objtype == OBJECT_SUBSCRIPTION)
+ return NULL;
+
+ /*
+ * Constraints are sufficiently different that it is easier to handle them
+ * separately.
+ */
+ if (node->objtype == OBJECT_DOMCONSTRAINT ||
+ node->objtype == OBJECT_TABCONSTRAINT)
+ {
+ Assert(address.classId == ConstraintRelationId);
+ return deparse_CommentOnConstraintSmt(address.objectId, parsetree);
+ }
+
+ ret = new_objtree_VA("COMMENT ON %{objtype}s", 1,
+ "objtype", ObjTypeString,
+ (char *) stringify_objtype(node->objtype, false));
+
+ /*
+ * Add the object identity clause. For zero argument aggregates we need
+ * to add the (*) bit; in all other cases we can just use
+ * getObjectIdentity.
+ *
+ * XXX shouldn't we instead fix the object identities for zero-argument
+ * aggregates?
+ */
+ if (node->objtype == OBJECT_AGGREGATE)
+ {
+ HeapTuple procTup;
+ Form_pg_proc procForm;
+
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(address.objectId));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for procedure with OID %u",
+ address.objectId);
+ procForm = (Form_pg_proc) GETSTRUCT(procTup);
+ if (procForm->pronargs == 0)
+ identity = psprintf("%s(*)",
+ quote_qualified_identifier(get_namespace_name(procForm->pronamespace),
+ NameStr(procForm->proname)));
+ else
+ identity = getObjectIdentity(&address, false);
+ ReleaseSysCache(procTup);
+ }
+ else
+ identity = getObjectIdentity(&address, false);
+
+ append_string_object(ret, "%{identity}s", "identity", identity);
+
+ /* Add the comment clause; can be either NULL or a quoted literal. */
+ append_literal_or_null(ret, "IS %{comment}s", node->comment);
+
+ return ret;
+}
+
+/*
+ * Deparse a SECURITY LABEL command.
+ *
+ * Verbose syntax
+ * SECURITY LABEL FOR %{provider}s ON %{object_type_name}s %{identity}s IS %{label}s
+ */
+static ObjTree *
+deparse_SecLabelStmt(CollectedCommand *cmd)
+{
+ ObjTree *ret;
+ SecLabelStmt *node = (SecLabelStmt *) cmd->parsetree;
+
+ /* Don't deparse sql commands generated while creating extension */
+ if (cmd->in_extension)
+ return NULL;
+
+ Assert(cmd->d.seclabel.provider);
+
+ ret = new_objtree_VA("SECURITY LABEL FOR %{provider}s ON %{objtype}s %{identity}s", 3,
+ "provider", ObjTypeString, cmd->d.seclabel.provider,
+ "objtype", ObjTypeString, stringify_objtype(node->objtype, false),
+ "identity", ObjTypeString, getObjectIdentity(&(cmd->d.seclabel.address), false));
+
+ /* Add the label clause; can be either NULL or a quoted literal. */
+ append_literal_or_null(ret, "IS %{label}s", node->label);
+
+ return ret;
+}
+
+/*
+ * Deparse a CreateAmStmt (CREATE ACCESS METHOD).
+ *
+ * Given an access method OID and the parse tree that created it, return an
+ * ObjTree representing the CREATE ACCESS METHOD command.
+ *
+ * Verbose syntax
+ * CREATE ACCESS METHOD %{identity}I TYPE %{am_type}s HANDLER %{handler}D
+ */
+static ObjTree *
+deparse_CreateAmStmt(Oid objectId, Node *parsetree)
+{
+ ObjTree *ret;
+ HeapTuple amTup;
+ Form_pg_am amForm;
+ char *amtype;
+
+ amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(amTup))
+ elog(ERROR, "cache lookup failed for access method with OID %u",
+ objectId);
+ amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+ switch (amForm->amtype)
+ {
+ case 'i':
+ amtype = "INDEX";
+ break;
+ case 't':
+ amtype = "TABLE";
+ break;
+ default:
+ elog(ERROR, "invalid type %c for access method", amForm->amtype);
+ }
+
+ ret = new_objtree_VA("CREATE ACCESS METHOD %{identity}I TYPE %{am_type}s HANDLER %{handler}D", 3,
+ "identity", ObjTypeString,
+ NameStr(amForm->amname),
+ "am_type", ObjTypeString, amtype,
+ "handler", ObjTypeObject,
+ new_objtree_for_qualname_id(ProcedureRelationId,
+ amForm->amhandler));
+
+ ReleaseSysCache(amTup);
+
+ return ret;
+}
+
+/*
+ * Deparse the publication column list.
+ *
+ * Given a tuple of pg_publication_rel, return an objTree that represent the
+ * column names.
+ */
+static ObjTree *
+deparse_PublicationRelationColumnList(HeapTuple pubreltup)
+{
+ bool isnull;
+ List *collist = NIL;
+ ObjTree *columnlist;
+ Datum attrsdatum;
+
+ attrsdatum = SysCacheGetAttr(PUBLICATIONRELMAP, pubreltup,
+ Anum_pg_publication_rel_prattrs,
+ &isnull);
+
+ columnlist = new_objtree("");
+ if (!isnull)
+ {
+ Form_pg_publication_rel pubrel;
+ ArrayType *arr;
+ int nelems;
+ int16 *elems;
+
+ arr = DatumGetArrayTypeP(attrsdatum);
+ nelems = ARR_DIMS(arr)[0];
+ elems = (int16 *) ARR_DATA_PTR(arr);
+
+ pubrel = (Form_pg_publication_rel) GETSTRUCT(pubreltup);
+
+ for (int i = 0; i < nelems; i++)
+ {
+ char *colname = get_attname(pubrel->prrelid, elems[i], false);
+ collist = lappend(collist, new_string_object(colname));
+ }
+
+ append_array_object(columnlist, "(%{cols:, }s)", collist);
+ }
+ else
+ append_not_present(columnlist);
+
+ return columnlist;
+}
+
+/*
+ * Deparse the publication where clause.
+ *
+ * Given a tuple of pg_publication_rel, return a objTree that represent the
+ * publication where clause.
+ */
+static ObjTree *
+deparse_PublicationRelationWhereClause(HeapTuple pubreltup)
+{
+ Datum qualdatum;
+ ObjTree *whereclause;
+ bool isnull;
+
+ qualdatum = SysCacheGetAttr(PUBLICATIONRELMAP, pubreltup,
+ Anum_pg_publication_rel_prqual, &isnull);
+
+ whereclause = new_objtree("");
+ if (!isnull)
+ {
+ Form_pg_publication_rel pubrel;
+ Node *qualnode;
+ List *context;
+ char *qualstr;
+ Oid relid;
+
+ pubrel = (Form_pg_publication_rel) GETSTRUCT(pubreltup);
+ relid = pubrel->prrelid;
+
+ context = deparse_context_for(get_rel_name(relid), relid);
+ qualnode = stringToNode(TextDatumGetCString(qualdatum));
+ qualstr = deparse_expression(qualnode, context, false, false);
+
+ append_string_object(whereclause, "WHERE %{where}s", "where", qualstr);
+ }
+ else
+ append_not_present(whereclause);
+
+ return whereclause;
+}
+
+/*
+ * Subroutine for CREATE PUBLICATION deparsing.
+ *
+ * Deal with all the publication table information including the where clause
+ * and column list.
+ */
+static List *
+deparse_PublicationRelationDefs(Oid puboid)
+{
+ Relation pubrelsrel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tup;
+ List *reldefs = NIL;
+
+ pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey,
+ Anum_pg_publication_rel_prpubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(puboid));
+
+ scan = systable_beginscan(pubrelsrel, PublicationRelPrpubidIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_publication_rel pubrel;
+ Oid relid;
+ ObjTree *columnlist;
+ ObjTree *whereclause;
+ ObjTree *reldef;
+
+ pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+ relid = pubrel->prrelid;
+
+ reldef = new_objtree_VA("%{tablename}D", 1,
+ "tablename", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId, relid));
+
+ columnlist = deparse_PublicationRelationColumnList(tup);
+ append_object_object(reldef, "%{column_list}s", columnlist);
+
+ whereclause = deparse_PublicationRelationWhereClause(tup);
+ append_object_object(reldef, "%{where_clause}s", whereclause);
+
+ reldefs = lappend(reldefs, new_object_object(reldef));
+ }
+
+ systable_endscan(scan);
+ table_close(pubrelsrel, AccessShareLock);
+
+ return reldefs;
+}
+
+/*
+ * Deparse a CreatePublicationStmt (CREATE PUBLICATION).
+ *
+ * Given an publication OID and the parse tree that created it, return an
+ * ObjTree representing the CREATE PUBLICATION command.
*
* Verbose syntax
- * ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I
+ * CREATE PUBLICATION %{identity}I %{for_tables}s %{for_schemas}s %{with_clause}s
* OR
- * ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I
+ * CREATE PUBLICATION %{identity}I FOR ALL TABLES %{with_clause}s
+ */
+static ObjTree *
+deparse_CreatePublicationStmt(Oid objectId, Node *parsetree)
+{
+ CreatePublicationStmt *node = (CreatePublicationStmt *) parsetree;
+ ObjTree *createPub;
+ ObjTree *tmp;
+ HeapTuple pubTup;
+ Form_pg_publication pubForm;
+ List *list = NIL;
+ ListCell *cell;
+
+ pubTup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(pubTup))
+ elog(ERROR, "cache lookup failed for access method with OID %u",
+ objectId);
+ pubForm = (Form_pg_publication) GETSTRUCT(pubTup);
+
+ createPub = new_objtree_VA("CREATE PUBLICATION %{identity}s", 1,
+ "identity", ObjTypeString,
+ NameStr(pubForm->pubname));
+
+ if (node->for_all_tables)
+ append_format_string(createPub, "FOR ALL TABLES");
+ else
+ {
+ List *oldschemaids;
+ StringInfoData for_schemas;
+
+ /* FOR TABLES t1,t2,... */
+ tmp = new_objtree("FOR TABLE");
+
+ list = deparse_PublicationRelationDefs(objectId);
+ if (list != NIL)
+ append_array_object(tmp, "%{tables:, }s", list);
+ else
+ append_not_present(tmp);
+
+ append_object_object(createPub, "%{for_tables}s", tmp);
+
+ /* FOR TABLES IN SCHEMA s1,s2,... */
+ initStringInfo(&for_schemas);
+ if (list != NIL)
+ appendStringInfoString(&for_schemas, ", ");
+ else
+ appendStringInfoString(&for_schemas, "FOR ");
+ appendStringInfoString(&for_schemas, "TABLES IN SCHEMA");
+
+ tmp = new_objtree(for_schemas.data);
+ list = NIL;
+ oldschemaids = GetPublicationSchemas(pubForm->oid);
+ foreach(cell, oldschemaids)
+ {
+ Oid schemaid = lfirst_oid(cell);
+ list = lappend(list, new_string_object(get_namespace_name(schemaid)));
+ }
+
+ if (list != NIL)
+ append_array_object(tmp, "%{schemas:, }I", list);
+ else
+ append_not_present(tmp);
+
+ append_object_object(createPub, "%{for_schemas}s", tmp);
+ }
+
+ /* WITH clause */
+ tmp = new_objtree("WITH");
+
+ list = NIL;
+ foreach(cell, node->options)
+ {
+ ObjTree *tmp_obj;
+ DefElem *opt = (DefElem *) lfirst(cell);
+
+ tmp_obj = deparse_DefElem(opt, false);
+ list = lappend(list, new_object_object(tmp_obj));
+ }
+
+ if (list != NIL)
+ append_array_object(tmp, "(%{with:, }s)", list);
+ else
+ append_not_present(tmp);
+
+ append_object_object(createPub, "%{with_clause}s", tmp);
+
+ ReleaseSysCache(pubTup);
+
+ return createPub;
+}
+
+/*
+ * Deparse a AlterPublicationStmt (ALTER PUBLICATION).
+ *
+ * Given an publication relation OID or publication schema OID and the parse
+ * tree that added it, return an ObjTree representing the ALTER PUBLICATION
+ * command.
+ *
+ * Note that only ALTER PUBLICATION ADD/SET should be deparsed here, ALTER
+ * PUBLICATION DROP is deparsed in different places.
+ *
+ * XXX ALTER PUBLICATION SET publication_object is converted to ALTER
+ * PUBLICATION ADD/DROP.
+ *
+ * Verbose syntax
+ * ALTER PUBLICATION %{identity}I %{add_object}s
* OR
- * ALTER %{objtype}s %{if_exists}s %{only}s %{identity}D RENAME COLUMN %{colname}I TO %{newname}I %{cascade}s
+ * ALTER PUBLICATION %{identity}I %{set_options}s
*/
static ObjTree *
-deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+deparse_AlterPublicationAddStmt(ObjectAddress object, Node *parsetree)
{
- RenameStmt *node = (RenameStmt *) parsetree;
- ObjTree *ret;
- Relation relation;
- Oid schemaId;
+ char *pubname;
+ ObjTree *alterpub;
+ ObjTree *addobject = NULL;
+ ObjTree *setoption = NULL;
+ AlterPublicationStmt *node = (AlterPublicationStmt *) parsetree;
- /*
- * 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)
+ switch (object.classId)
{
- case OBJECT_INDEX:
- case OBJECT_TABLE:
- relation = relation_open(address.objectId, AccessShareLock);
- schemaId = RelationGetNamespace(relation);
- ret = new_objtree_VA("ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I", 4,
- "objtype", ObjTypeString,
- stringify_objtype(node->renameType, false),
- "if_exists", ObjTypeString,
- node->missing_ok ? "IF EXISTS" : "",
- "identity", ObjTypeObject,
- new_objtree_for_qualname(schemaId,
- node->relation->relname),
- "newname", ObjTypeString,
- node->newname);
- relation_close(relation, AccessShareLock);
+ /* ADD TABLE */
+ case PublicationRelRelationId:
+ {
+ ObjTree *whereclause;
+ ObjTree *columnlist;
+ Form_pg_publication_rel pubrelform;
+ Oid relid;
+ HeapTuple tup;
+
+ tup = SearchSysCache1(PUBLICATIONREL,
+ ObjectIdGetDatum(object.objectId));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication table %u",
+ object.objectId);
+
+ pubrelform = (Form_pg_publication_rel) GETSTRUCT(tup);
+ relid = pubrelform->prrelid;
+
+ addobject = new_objtree_VA("ADD TABLE %{tablename}D", 1,
+ "tablename", ObjTypeObject,
+ new_objtree_for_qualname_id(RelationRelationId, relid));
+
+ columnlist = deparse_PublicationRelationColumnList(tup);
+ append_object_object(addobject, "%{column_list}s", columnlist);
+
+ whereclause = deparse_PublicationRelationWhereClause(tup);
+ append_object_object(addobject, "%{where_clause}s", whereclause);
+
+ ReleaseSysCache(tup);
+ }
break;
- case OBJECT_TABCONSTRAINT:
+ /* ADD TABLES IN SCHEMA */
+ case PublicationNamespaceRelationId:
{
- HeapTuple constrtup;
- Form_pg_constraint constform;
+ Form_pg_publication_namespace pubnspform;
+ HeapTuple tup;
- 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);
+ tup = SearchSysCache1(PUBLICATIONNAMESPACE,
+ ObjectIdGetDatum(object.objectId));
- ret = new_objtree_VA("ALTER TABLE %{only}s %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I", 4,
- "only", ObjTypeString,
- node->relation->inh ? "" : "ONLY",
- "identity", ObjTypeObject,
- new_objtree_for_qualname_id(RelationRelationId,
- constform->conrelid),
- "oldname", ObjTypeString, node->subname,
- "newname", ObjTypeString, node->newname);
- ReleaseSysCache(constrtup);
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for publication schema %u",
+ object.objectId);
+
+ pubnspform = (Form_pg_publication_namespace) GETSTRUCT(tup);
+
+ addobject = new_objtree_VA("ADD TABLES IN SCHEMA %{schema}I", 1,
+ "schema", ObjTypeString,
+ get_namespace_name(pubnspform->pnnspid));
+
+ ReleaseSysCache(tup);
}
break;
- case OBJECT_COLUMN:
- relation = relation_open(address.objectId, AccessShareLock);
- schemaId = RelationGetNamespace(relation);
-
- ret = new_objtree_VA("ALTER %{objtype}s", 1,
- "objtype", ObjTypeString,
- stringify_objtype(node->relationType, false));
+ /* SET option */
+ case PublicationRelationId:
+ {
+ List *optionlist = NIL;
+ ListCell *cell;
- /* Composite types do not support IF EXISTS */
- if (node->renameType == OBJECT_COLUMN)
- append_string_object(ret, "%{if_exists}s",
- "if_exists",
- node->missing_ok ? "IF EXISTS" : "");
- if (!node->relation->inh)
- append_string_object(ret, "%{only}s",
- "only",
- "ONLY");
- append_object_object(ret, "%{identity}D",
- new_objtree_for_qualname(schemaId,
- node->relation->relname));
- append_string_object(ret, "RENAME COLUMN %{colname}I",
- "colname", node->subname);
+ Assert(node->options != NIL);
- append_string_object(ret, "TO %{newname}I", "newname",
- node->newname);
+ foreach(cell, node->options)
+ {
+ ObjTree *tmp_obj;
+ DefElem *opt = (DefElem *) lfirst(cell);
- if (node->renameType == OBJECT_ATTRIBUTE)
- append_object_object(ret, "%{cascade}s",
- new_objtree_VA("CASCADE", 1,
- "present", ObjTypeBool,
- node->behavior == DROP_CASCADE));
+ tmp_obj = deparse_DefElem(opt, false);
+ optionlist = lappend(optionlist, new_object_object(tmp_obj));
+ }
- relation_close(relation, AccessShareLock);
+ setoption = new_objtree_VA("SET (%{options:, }s)", 1,
+ "options", ObjTypeArray,
+ optionlist);
+ }
break;
default:
- elog(ERROR, "unsupported object type %d", node->renameType);
+ Assert(false);
+ break;
}
- return ret;
+ pubname = pstrdup(node->pubname);
+
+ alterpub = new_objtree_VA("ALTER PUBLICATION %{identity}s", 1,
+ "identity", ObjTypeString, pubname);
+
+ Assert(addobject || setoption);
+
+ if (addobject)
+ append_object_object(alterpub, "%{add_object}s", addobject);
+ else
+ append_object_object(alterpub, "%{set_options}s", setoption);
+
+ return alterpub;
}
/*
- * Deparse a AlterObjectDependsStmt (ALTER ... DEPENDS ON ...).
+ * Handle deparsing of ALTER PUBLICATION DROP commands.
*
* Verbose syntax
- * ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I
+ * ALTER PUBLICATION %{identity}I %{drop_object}s
*/
-static ObjTree *
-deparse_AlterDependStmt(Oid objectId, Node *parsetree)
+char *
+deparse_AlterPublicationDropStmt(SQLDropObject *obj)
{
- AlterObjectDependsStmt *node = (AlterObjectDependsStmt *) parsetree;
- ObjTree *ret = NULL;
+ ObjTree *alterpub;
+ ObjTree *drop_object = NULL;
+ char objname[NAMEDATALEN];
+ char pubname[NAMEDATALEN];
+ Jsonb *jsonb;
+ char *command;
+ StringInfoData str;
- if (node->objectType == OBJECT_INDEX)
+ if (sscanf(obj->objidentity, "%s in publication %s", objname, pubname) != 2)
+ elog(ERROR, "could not parse ALTER PUBLICATION command \"%s\"",
+ obj->objidentity);
+
+ switch (getObjectClass(&obj->address))
{
- ObjTree *qualified;
- Relation relation = relation_open(objectId, AccessShareLock);
+ /* DROP TABLE */
+ case OCLASS_PUBLICATION_REL:
+ drop_object = new_objtree_VA("DROP TABLE %{tablename}s", 1,
+ "tablename", ObjTypeString, objname);
+ break;
- qualified = new_objtree_for_qualname(relation->rd_rel->relnamespace,
- node->relation->relname);
- relation_close(relation, AccessShareLock);
+ /* DROP TABLES IN SCHEMA */
+ case OCLASS_PUBLICATION_NAMESPACE:
+ drop_object = new_objtree_VA("DROP TABLES IN SCHEMA %{schemaname}s", 1,
+ "schemaname", ObjTypeString, objname);
+ break;
- ret = new_objtree_VA("ALTER INDEX %{identity}D %{no}s DEPENDS ON EXTENSION %{newname}I", 3,
- "identity", ObjTypeObject, qualified,
- "no", ObjTypeString,
- node->remove ? "NO" : "",
- "newname", ObjTypeString, strVal(node->extname));
+ default:
+ Assert(false);
+ break;
}
- else
- elog(LOG, "unrecognized node type in deparse command: %d",
- (int) nodeTag(parsetree));
- return ret;
+ alterpub = new_objtree_VA("ALTER PUBLICATION %{identity}s", 1,
+ "identity", ObjTypeString, pubname);
+
+ Assert(drop_object);
+
+ append_object_object(alterpub, "%{drop_object}s", drop_object);
+
+ initStringInfo(&str);
+ jsonb = objtree_to_jsonb(alterpub, NULL /* Owner/role can be skipped for drop command */);
+ command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+
+ return command;
}
/*
@@ -3666,6 +9896,40 @@ deparse_simple_command(CollectedCommand *cmd, bool *include_owner)
/* This switch needs to handle everything that ProcessUtilitySlow does */
switch (nodeTag(parsetree))
{
+ case T_AlterCollationStmt:
+ *include_owner = false;
+ return deparse_AlterCollation(objectId, parsetree);
+
+ case T_AlterDomainStmt:
+ *include_owner = false;
+ return deparse_AlterDomainStmt(objectId, parsetree,
+ cmd->d.simple.secondaryObject);
+
+ case T_AlterEnumStmt:
+ *include_owner = false;
+ return deparse_AlterEnumStmt(objectId, parsetree);
+
+ case T_AlterExtensionContentsStmt:
+ *include_owner = false;
+ return deparse_AlterExtensionContentsStmt(objectId, parsetree,
+ cmd->d.simple.secondaryObject);
+
+ case T_AlterExtensionStmt:
+ *include_owner = false;
+ return deparse_AlterExtensionStmt(objectId, parsetree);
+
+ case T_AlterFdwStmt:
+ *include_owner = false;
+ return deparse_AlterFdwStmt(objectId, parsetree);
+
+ case T_AlterForeignServerStmt:
+ *include_owner = false;
+ return deparse_AlterForeignServerStmt(objectId, parsetree);
+
+ case T_AlterFunctionStmt:
+ *include_owner = false;
+ return deparse_AlterFunction(objectId, parsetree);
+
case T_AlterObjectDependsStmt:
*include_owner = false;
return deparse_AlterDependStmt(objectId, parsetree);
@@ -3676,20 +9940,138 @@ deparse_simple_command(CollectedCommand *cmd, bool *include_owner)
parsetree,
cmd->d.simple.secondaryObject);
+ case T_AlterOperatorStmt:
+ *include_owner = false;
+ return deparse_AlterOperatorStmt(objectId, parsetree);
+
case T_AlterOwnerStmt:
*include_owner = false;
return deparse_AlterOwnerStmt(cmd->d.simple.address, parsetree);
+ case T_AlterPolicyStmt:
+ *include_owner = false;
+ return deparse_AlterPolicyStmt(objectId, parsetree);
+
+ case T_AlterSeqStmt:
+ *include_owner = false;
+ return deparse_AlterSeqStmt(objectId, parsetree);
+
+ case T_AlterStatsStmt:
+ *include_owner = false;
+ return deparse_AlterStatsStmt(objectId, parsetree);
+
+ case T_AlterTSDictionaryStmt:
+ *include_owner = false;
+ return deparse_AlterTSDictionaryStmt(objectId, parsetree);
+
+ case T_AlterTypeStmt:
+ *include_owner = false;
+ return deparse_AlterTypeSetStmt(objectId, parsetree);
+
+ case T_AlterUserMappingStmt:
+ *include_owner = false;
+ return deparse_AlterUserMappingStmt(objectId, parsetree);
+
+ case T_CommentStmt:
+ *include_owner = false;
+ return deparse_CommentStmt(cmd->d.simple.address, parsetree);
+
+ case T_CompositeTypeStmt:
+ return deparse_CompositeTypeStmt(objectId, parsetree);
+
+ case T_CreateAmStmt:
+ return deparse_CreateAmStmt(objectId, parsetree);
+
+ case T_CreateCastStmt:
+ return deparse_CreateCastStmt(objectId, parsetree);
+
+ case T_CreateConversionStmt:
+ return deparse_CreateConversion(objectId, parsetree);
+
+ case T_CreateDomainStmt:
+ return deparse_CreateDomain(objectId, parsetree);
+
+ case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */
+ return deparse_CreateEnumStmt(objectId, parsetree);
+
+ case T_CreateExtensionStmt:
+ return deparse_CreateExtensionStmt(objectId, parsetree);
+
+ case T_CreateFdwStmt:
+ return deparse_CreateFdwStmt(objectId, parsetree);
+
+ case T_CreateForeignServerStmt:
+ return deparse_CreateForeignServerStmt(objectId, parsetree);
+
+ case T_CreateFunctionStmt:
+ return deparse_CreateFunction(objectId, parsetree);
+
+ case T_CreateOpFamilyStmt:
+ return deparse_CreateOpFamily(objectId, parsetree);
+
+ case T_CreatePLangStmt:
+ return deparse_CreateLangStmt(objectId, parsetree);
+
+ case T_CreatePolicyStmt:
+ return deparse_CreatePolicyStmt(objectId, parsetree);
+
+ case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */
+ return deparse_CreateRangeStmt(objectId, parsetree);
+
+ case T_CreateSchemaStmt:
+ return deparse_CreateSchemaStmt(objectId, parsetree);
+
+ case T_CreateSeqStmt:
+ return deparse_CreateSeqStmt(objectId, parsetree);
+
+ case T_CreateStatsStmt:
+ return deparse_CreateStatisticsStmt(objectId, parsetree);
+
case T_CreateStmt:
return deparse_CreateStmt(objectId, parsetree);
+ case T_CreateForeignTableStmt:
+ return deparse_CreateForeignTableStmt(objectId, parsetree);
+
+ case T_CreateTableAsStmt: /* CREATE MATERIALIZED VIEW */
+ return deparse_CreateTableAsStmt_vanilla(objectId, parsetree);
+
+ case T_CreateTransformStmt:
+ return deparse_CreateTransformStmt(objectId, parsetree);
+
+ case T_CreateTrigStmt:
+ return deparse_CreateTrigStmt(objectId, parsetree);
+
+ case T_CreateUserMappingStmt:
+ return deparse_CreateUserMappingStmt(objectId, parsetree);
+
+ case T_DefineStmt:
+ return deparse_DefineStmt(objectId, parsetree,
+ cmd->d.simple.secondaryObject);
+
case T_IndexStmt:
return deparse_IndexStmt(objectId, parsetree);
+ case T_RefreshMatViewStmt:
+ *include_owner = false;
+ return deparse_RefreshMatViewStmt(objectId, parsetree);
+
case T_RenameStmt:
*include_owner = false;
return deparse_RenameStmt(cmd->d.simple.address, parsetree);
+ case T_RuleStmt:
+ return deparse_RuleStmt(objectId, parsetree);
+
+ case T_ViewStmt: /* CREATE VIEW */
+ return deparse_ViewStmt(objectId, parsetree);
+
+ case T_CreatePublicationStmt:
+ return deparse_CreatePublicationStmt(objectId, parsetree);
+
+ case T_AlterPublicationStmt:
+ return deparse_AlterPublicationAddStmt(cmd->d.simple.address, parsetree);
+
default:
elog(LOG, "unrecognized node type in deparse command: %d",
(int) nodeTag(parsetree));
@@ -3755,9 +10137,32 @@ deparse_utility_command(CollectedCommand *cmd, bool include_owner, bool verbose_
tree = deparse_AlterRelation(cmd);
include_owner = false;
break;
+ case SCT_Grant:
+ tree = deparse_GrantStmt(cmd);
+ include_owner = false;
+ break;
case SCT_CreateTableAs:
tree = deparse_CreateTableAsStmt(cmd);
break;
+ case SCT_AlterOpFamily:
+ include_owner = false;
+ tree = deparse_AlterOpFamily(cmd);
+ break;
+ case SCT_CreateOpClass:
+ tree = deparse_CreateOpClassStmt(cmd);
+ break;
+ case SCT_AlterDefaultPrivileges:
+ include_owner = false;
+ tree = deparse_AlterDefaultPrivilegesStmt(cmd);
+ break;
+ case SCT_AlterTSConfig:
+ include_owner = false;
+ tree = deparse_AlterTSConfigurationStmt(cmd);
+ break;
+ case SCT_SecurityLabel:
+ include_owner = false;
+ tree = deparse_SecLabelStmt(cmd);
+ break;
default:
elog(ERROR, "unexpected deparse node type %d", cmd->type);
}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 38c11f000c..1b75003b5f 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2074,6 +2074,37 @@ EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
MemoryContextSwitchTo(oldcxt);
}
+/*
+ * EventTriggerCollectSecLabel
+ * Save data about an SECURITY LABEL command being executed
+ */
+void
+EventTriggerCollectSecLabel(ObjectAddress address, char *provider,
+ SecLabelStmt *stmt)
+{
+ MemoryContext oldcxt;
+ CollectedCommand *command;
+
+ /* ignore if event trigger context not set, or collection disabled */
+ if (!currentEventTriggerState ||
+ currentEventTriggerState->commandCollectionInhibited)
+ return;
+
+ oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+ command = palloc0(sizeof(CollectedCommand));
+ command->type = SCT_SecurityLabel;
+ command->in_extension = creating_extension;
+ command->role = GetUserNameFromId(GetUserId(), false);
+ command->d.seclabel.address = address;
+ command->d.seclabel.provider = provider;
+ command->parsetree = (Node *) copyObject(stmt);
+
+ currentEventTriggerState->commandList =
+ lappend(currentEventTriggerState->commandList, command);
+ MemoryContextSwitchTo(oldcxt);
+}
+
/*
* In a ddl_command_end event trigger, this function reports the DDL commands
* being run.
@@ -2125,6 +2156,7 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
case SCT_AlterOpFamily:
case SCT_CreateOpClass:
case SCT_AlterTSConfig:
+ case SCT_SecurityLabel:
case SCT_CreateTableAs:
{
char *identity;
@@ -2143,6 +2175,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
addr = cmd->d.createopc.address;
else if (cmd->type == SCT_AlterTSConfig)
addr = cmd->d.atscfg.address;
+ else if (cmd->type == SCT_SecurityLabel)
+ addr = cmd->d.seclabel.address;
else if (cmd->type == SCT_CreateTableAs)
addr = cmd->d.ctas.address;
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index e3aad1c883..a8a9175d15 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -104,6 +104,7 @@ parse_publication_options(ParseState *pstate,
pubactions->pubtruncate = true;
pubactions->pubddl_table = false;
pubactions->pubddl_index = false;
+ pubactions->pubddl_all = false;
*publish_via_partition_root = false;
/* Parse options */
@@ -180,6 +181,7 @@ parse_publication_options(ParseState *pstate,
*/
pubactions->pubddl_table = false;
pubactions->pubddl_index = false;
+ pubactions->pubddl_all = false;
*ddl_type_given = true;
ddl_level = defGetString(defel);
@@ -194,7 +196,9 @@ parse_publication_options(ParseState *pstate,
{
char *publish_opt = (char *) lfirst(lc3);
- if (strcmp(publish_opt, "table") == 0)
+ if (strcmp(publish_opt, "all") == 0)
+ pubactions->pubddl_all = true;
+ else if (strcmp(publish_opt, "table") == 0)
pubactions->pubddl_table = true;
else if (strcmp(publish_opt, "index") == 0)
pubactions->pubddl_index = true;
@@ -847,28 +851,39 @@ CreateDDLReplicaEventTriggers(PublicationActions pubactions, Oid puboid)
List *init_commands = NIL;
List *end_commands = NIL;
- if (pubactions.pubddl_table)
+ if (pubactions.pubddl_all)
{
- start_commands = lappend_int(start_commands, CMDTAG_DROP_TABLE);
+ start_commands = end_commands = init_commands =
+ GetCommandTagsForDDLRepl();
+
rewrite_commands = lappend_int(rewrite_commands, CMDTAG_ALTER_TABLE);
+ }
+ else
+ {
+ if (pubactions.pubddl_table)
+ {
+ start_commands = lappend_int(start_commands, CMDTAG_DROP_TABLE);
+ rewrite_commands = lappend_int(rewrite_commands, CMDTAG_ALTER_TABLE);
- init_commands = lappend_int(init_commands, CMDTAG_CREATE_TABLE_AS);
- init_commands = lappend_int(init_commands, CMDTAG_SELECT_INTO);
+ init_commands = lappend_int(init_commands, CMDTAG_CREATE_TABLE_AS);
+ init_commands = lappend_int(init_commands, CMDTAG_SELECT_INTO);
- end_commands = lappend_int(end_commands, CMDTAG_CREATE_TABLE);
- end_commands = lappend_int(end_commands, CMDTAG_ALTER_TABLE);
- end_commands = lappend_int(end_commands, CMDTAG_DROP_TABLE);
- }
+ end_commands = lappend_int(end_commands, CMDTAG_CREATE_TABLE);
+ end_commands = lappend_int(end_commands, CMDTAG_ALTER_TABLE);
+ end_commands = lappend_int(end_commands, CMDTAG_DROP_TABLE);
+ }
- if (pubactions.pubddl_index)
- {
- start_commands = lappend_int(start_commands, CMDTAG_DROP_INDEX);
+ if (pubactions.pubddl_index)
+ {
+ start_commands = lappend_int(start_commands, CMDTAG_DROP_INDEX);
- end_commands = lappend_int(end_commands, CMDTAG_CREATE_INDEX);
- end_commands = lappend_int(end_commands, CMDTAG_ALTER_INDEX);
- end_commands = lappend_int(end_commands, CMDTAG_DROP_INDEX);
+ end_commands = lappend_int(end_commands, CMDTAG_CREATE_INDEX);
+ end_commands = lappend_int(end_commands, CMDTAG_ALTER_INDEX);
+ end_commands = lappend_int(end_commands, CMDTAG_DROP_INDEX);
+ }
}
+
/* Create the ddl_command_end event trigger */
if (end_commands != NIL)
CreateDDLReplicaEventTrigger(PUB_TRIG_EVENT1, end_commands, puboid);
@@ -1007,6 +1022,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
BoolGetDatum(pubactions.pubddl_table);
values[Anum_pg_publication_pubddl_index - 1] =
BoolGetDatum(pubactions.pubddl_index);
+ values[Anum_pg_publication_pubddl_all - 1] =
+ BoolGetDatum(pubactions.pubddl_all);
values[Anum_pg_publication_pubviaroot - 1] =
BoolGetDatum(publish_via_partition_root);
@@ -1203,7 +1220,9 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
}
- if (ddl_type_given && (pubactions.pubddl_table || pubactions.pubddl_index))
+ if (ddl_type_given &&
+ (pubactions.pubddl_table || pubactions.pubddl_index ||
+ pubactions.pubddl_all))
{
if (root_relids == NIL)
root_relids = GetPublicationRelations(pubform->oid,
@@ -1239,7 +1258,8 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
{
/* Recreate the event triggers if the ddl option is changed. */
if (pubform->pubddl_table != pubactions.pubddl_table ||
- pubform->pubddl_index != pubactions.pubddl_index)
+ pubform->pubddl_index != pubactions.pubddl_index ||
+ pubform->pubddl_all != pubactions.pubddl_all)
{
DropDDLReplicaEventTriggers(pubform->oid);
CreateDDLReplicaEventTriggers(pubactions, pubform->oid);
@@ -1250,6 +1270,9 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
values[Anum_pg_publication_pubddl_index - 1] = BoolGetDatum(pubactions.pubddl_index);
replaces[Anum_pg_publication_pubddl_index - 1] = true;
+
+ values[Anum_pg_publication_pubddl_all - 1] = BoolGetDatum(pubactions.pubddl_all);
+ replaces[Anum_pg_publication_pubddl_all - 1] = true;
}
if (publish_via_partition_root_given)
@@ -1357,7 +1380,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
if (stmt->action == AP_AddObjects)
{
- if (pubform->pubddl_table || pubform->pubddl_index)
+ if (pubform->pubddl_table || pubform->pubddl_index ||
+ pubform->pubddl_all)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
@@ -1381,7 +1405,8 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
List *delrels = NIL;
ListCell *oldlc;
- if (pubform->pubddl_table || pubform->pubddl_index)
+ if (pubform->pubddl_table || pubform->pubddl_index ||
+ pubform->pubddl_all)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7ff16e3276..7ee54cde8e 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -18,6 +18,7 @@
#include "catalog/indexing.h"
#include "catalog/pg_seclabel.h"
#include "catalog/pg_shseclabel.h"
+#include "commands/event_trigger.h"
#include "commands/seclabel.h"
#include "miscadmin.h"
#include "utils/builtins.h"
@@ -213,6 +214,11 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
if (relation != NULL)
relation_close(relation, NoLock);
+ /* Pass the info to event triggers about the SECURITY LABEL. */
+ if (EventTriggerSupportsObjectType(stmt->objtype))
+ EventTriggerCollectSecLabel(address, pstrdup(provider->provider_name),
+ stmt);
+
return address;
}
diff --git a/src/backend/replication/logical/ddltrigger.c b/src/backend/replication/logical/ddltrigger.c
index 11e33b9b65..86c520ba11 100644
--- a/src/backend/replication/logical/ddltrigger.c
+++ b/src/backend/replication/logical/ddltrigger.c
@@ -226,21 +226,103 @@ publication_deparse_ddl_command_end(PG_FUNCTION_ARGS)
EventTriggerData *trigdata;
char *command;
DeparsedCommandType cmdtype;
+ const char *tmptype;
+ ObjectClass objclass;
+ ObjectAddress objaddr;
trigdata = (EventTriggerData *) fcinfo->context;
stmt = (DropStmt *) trigdata->parsetree;
obj = slist_container(SQLDropObject, next, iter.cur);
+ objaddr = obj->address;
+ objclass = getObjectClass(&objaddr);
if (strcmp(obj->objecttype, "table") == 0)
cmdtype = DCT_TableDropEnd;
- else if (strcmp(obj->objecttype, "index") == 0)
+ else if (objclass == OCLASS_SCHEMA ||
+ objclass == OCLASS_OPERATOR ||
+ objclass == OCLASS_OPCLASS ||
+ objclass == OCLASS_OPFAMILY ||
+ objclass == OCLASS_CAST ||
+ objclass == OCLASS_TYPE ||
+ objclass == OCLASS_TRIGGER ||
+ objclass == OCLASS_CONVERSION ||
+ objclass == OCLASS_POLICY ||
+ objclass == OCLASS_REWRITE ||
+ objclass == OCLASS_EXTENSION ||
+ objclass == OCLASS_FDW ||
+ objclass == OCLASS_TSCONFIG ||
+ objclass == OCLASS_TSDICT ||
+ objclass == OCLASS_TSTEMPLATE ||
+ objclass == OCLASS_TSPARSER ||
+ objclass == OCLASS_TRANSFORM ||
+ objclass == OCLASS_FOREIGN_SERVER ||
+ objclass == OCLASS_COLLATION ||
+ objclass == OCLASS_USER_MAPPING ||
+ objclass == OCLASS_LANGUAGE ||
+ objclass == OCLASS_STATISTIC_EXT ||
+ objclass == OCLASS_AM ||
+ objclass == OCLASS_PUBLICATION ||
+ objclass == OCLASS_PUBLICATION_REL ||
+ objclass == OCLASS_PUBLICATION_NAMESPACE ||
+ strcmp(obj->objecttype, "foreign table") == 0 ||
+ strcmp(obj->objecttype, "index") == 0 ||
+ strcmp(obj->objecttype, "sequence") == 0 ||
+ strcmp(obj->objecttype, "view") == 0 ||
+ strcmp(obj->objecttype, "function") == 0 ||
+ strcmp(obj->objecttype, "materialized view") == 0 ||
+ strcmp(obj->objecttype, "procedure") == 0 ||
+ strcmp(obj->objecttype, "routine") == 0 ||
+ strcmp(obj->objecttype, "aggregate") == 0)
cmdtype = DCT_ObjectDropEnd;
else
continue;
- command = deparse_drop_command(obj->objidentity, obj->objecttype,
- stmt->behavior);
+ /* Change foreign-data wrapper to foreign data wrapper */
+ if (strncmp(obj->objecttype, "foreign-data wrapper", 20) == 0)
+ {
+ tmptype = pstrdup("foreign data wrapper");
+ command = deparse_drop_command(obj->objidentity, tmptype,
+ stmt->behavior);
+ }
+
+ /* Change statistics object to statistics */
+ else if (strncmp(obj->objecttype, "statistics object",
+ strlen("statistics object")) == 0)
+ {
+ tmptype = pstrdup("statistics");
+ command = deparse_drop_command(obj->objidentity, tmptype,
+ stmt->behavior);
+ }
+
+ /*
+ * Object identity needs to be modified to make the drop work
+ *
+ * FROM: <role> on server <servername> TO : for <role> server
+ * <servername>
+ *
+ */
+ else if (strncmp(obj->objecttype, "user mapping", 12) == 0)
+ {
+ char *on_server;
+
+ tmptype = palloc(strlen(obj->objidentity) + 2);
+ on_server = strstr(obj->objidentity, "on server");
+
+ sprintf((char *) tmptype, "for ");
+ strncat((char *) tmptype, obj->objidentity, on_server - obj->objidentity);
+ strcat((char *) tmptype, on_server + 3);
+ command = deparse_drop_command(tmptype, obj->objecttype,
+ stmt->behavior);
+ }
+ else if (strncmp(obj->objecttype, "publication namespace",
+ strlen("publication namespace")) == 0 ||
+ strncmp(obj->objecttype, "publication relation",
+ strlen("publication relation")) == 0)
+ command = deparse_AlterPublicationDropStmt(obj);
+ else
+ command = deparse_drop_command(obj->objidentity, obj->objecttype,
+ stmt->behavior);
if (command)
LogLogicalDDLMessage("deparse", obj->address.objectId, cmdtype,
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 9a54cbd152..80cd6fad56 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -224,6 +224,7 @@ typedef struct DDLSyncCache
{
bool valid;
bool pubindex;
+ bool puball;
} DDLSyncCache;
static DDLSyncCache *ddlcache = NULL;
@@ -1754,6 +1755,8 @@ is_object_published(LogicalDecodingContext *ctx, Oid objid)
RelationSyncEntry *relentry;
PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
+ build_ddl_sync_cache(data);
+
/* First check the DDL command filter. */
switch (get_rel_relkind(objid))
{
@@ -1766,15 +1769,14 @@ is_object_published(LogicalDecodingContext *ctx, Oid objid)
* Skip sending this ddl if we don't publish ddl message or the ddl
* need to be published via its root relation.
*/
- if (!relentry->pubactions.pubddl_table ||
+ if ((!relentry->pubactions.pubddl_table &&
+ !ddlcache->puball) ||
relentry->publish_as_relid != objid)
return false;
break;
case RELKIND_INDEX:
- build_ddl_sync_cache(data);
-
- if (!ddlcache->pubindex)
+ if (!ddlcache->pubindex && !ddlcache->puball)
return false;
/* Get the table OID that the index is for. */
@@ -1787,7 +1789,8 @@ is_object_published(LogicalDecodingContext *ctx, Oid objid)
relentry = get_rel_sync_entry(data, relation);
RelationClose(relation);
- if (!relentry->pubactions.pubddl_table ||
+ if ((!relentry->pubactions.pubddl_table &&
+ !ddlcache->puball) ||
relentry->publish_as_relid != objid)
return false;
@@ -2240,7 +2243,8 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->streamed_txns = NIL;
entry->pubactions.pubinsert = entry->pubactions.pubupdate =
entry->pubactions.pubdelete = entry->pubactions.pubtruncate =
- entry->pubactions.pubddl_table = entry->pubactions.pubddl_index = false;
+ entry->pubactions.pubddl_table = entry->pubactions.pubddl_index =
+ entry->pubactions.pubddl_all = false;
entry->new_slot = NULL;
entry->old_slot = NULL;
memset(entry->exprstate, 0, sizeof(entry->exprstate));
@@ -2288,6 +2292,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->pubactions.pubtruncate = false;
entry->pubactions.pubddl_table = false;
entry->pubactions.pubddl_index = false;
+ entry->pubactions.pubddl_all = false;
/*
* Tuple slots cleanups. (Will be rebuilt later if needed).
@@ -2403,6 +2408,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate;
entry->pubactions.pubddl_table |= pub->pubactions.pubddl_table;
entry->pubactions.pubddl_index |= pub->pubactions.pubddl_index;
+ entry->pubactions.pubddl_all |= pub->pubactions.pubddl_all;
/*
* We want to publish the changes as the top-most ancestor
@@ -2488,6 +2494,7 @@ build_ddl_sync_cache(PGOutputData *data)
return;
ddlcache->pubindex = false;
+ ddlcache->puball = false;
reload_publications(data);
@@ -2496,6 +2503,7 @@ build_ddl_sync_cache(PGOutputData *data)
Publication *pub = lfirst(lc);
ddlcache->pubindex |= pub->pubactions.pubddl_index;
+ ddlcache->puball |= pub->pubactions.pubddl_all;
}
ddlcache->valid = true;
diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c
index ce09c5f233..7e03782524 100644
--- a/src/backend/tcop/cmdtag.c
+++ b/src/backend/tcop/cmdtag.c
@@ -14,6 +14,7 @@
#include "postgres.h"
#include "miscadmin.h"
+#include "nodes/pg_list.h"
#include "tcop/cmdtag.h"
#include "utils/builtins.h"
@@ -58,16 +59,15 @@ GetCommandTagNameAndLen(CommandTag commandTag, Size *len)
return tag_behavior[commandTag].name;
}
-CommandTag *
-GetCommandTagsForDDLRepl(int *ncommands)
+List *
+GetCommandTagsForDDLRepl(void)
{
- CommandTag *ddlrepl_commands = palloc0(COMMAND_TAG_NEXTTAG * sizeof(CommandTag));
- *ncommands = 0;
+ List *ddlrepl_commands = NIL;
for(int i = 0; i < COMMAND_TAG_NEXTTAG; i++)
{
if (tag_behavior[i].ddl_replication_ok)
- ddlrepl_commands[(*ncommands)++] = (CommandTag) i;
+ ddlrepl_commands = lappend_int(ddlrepl_commands, i);
}
return ddlrepl_commands;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 578a229ffc..34a9c853a6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1449,7 +1449,8 @@ ProcessUtilitySlow(ParseState *pstate,
address = DefineCollation(pstate,
stmt->defnames,
stmt->definition,
- stmt->if_not_exists);
+ stmt->if_not_exists,
+ &secondaryObject);
break;
default:
elog(ERROR, "unrecognized define stmt type: %d",
@@ -1839,6 +1840,7 @@ ProcessUtilitySlow(ParseState *pstate,
case T_SecLabelStmt:
address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
+ commandCollected = true;
break;
case T_CreateAmStmt:
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 296930eb3b..e46c39d620 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -307,6 +307,57 @@ format_procedure_qualified(Oid procedure_oid)
return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
}
+/*
+ * Append the parenthesized arguments of the given pg_proc row into the output
+ * buffer. force_qualify indicates whether to schema-qualify type names
+ * regardless of visibility.
+ */
+static void
+format_procedure_args_internal(Form_pg_proc procform, StringInfo buf,
+ bool force_qualify)
+{
+ int i;
+ char *(*func[2]) (Oid) = {format_type_be, format_type_be_qualified};
+
+ appendStringInfoChar(buf, '(');
+ for (i = 0; i < procform->pronargs; i++)
+ {
+ Oid thisargtype = procform->proargtypes.values[i];
+ char *argtype;
+
+ if (i > 0)
+ appendStringInfoChar(buf, ',');
+
+ argtype = func[force_qualify] (thisargtype);
+ appendStringInfoString(buf, argtype);
+ pfree(argtype);
+ }
+ appendStringInfoChar(buf, ')');
+}
+
+/*
+ * format_procedure_args - converts proc OID to "(args)"
+ */
+char *
+format_procedure_args(Oid procedure_oid, bool force_qualify)
+{
+ StringInfoData buf;
+ HeapTuple proctup;
+ Form_pg_proc procform;
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for procedure %u", procedure_oid);
+ procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+ initStringInfo(&buf);
+ format_procedure_args_internal(procform, &buf, force_qualify);
+
+ ReleaseSysCache(proctup);
+
+ return buf.data;
+}
+
/*
* format_procedure_extended - converts procedure OID to "pro_name(args)"
*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c8f5471b18..c489cac8dc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -35,6 +35,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -341,6 +342,8 @@ static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
static int decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
+static void pg_get_rule_whereclause(char *qualstr, Query *query,
+ StringInfo buf, int prettyFlags);
static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool keysOnly,
@@ -358,7 +361,6 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
-static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -555,6 +557,80 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(res));
}
+/*
+ * Given a pair of Datum corresponding to a rule's pg_rewrite.ev_qual and
+ * ev_action columns, return their text representation; ev_qual as a single
+ * string in whereClause and ev_action as a List of strings (which might be
+ * NIL, signalling NOTHING) in actions.
+ */
+void
+pg_get_ruledef_detailed(Datum ev_qual, Datum ev_action,
+ char **whereClause, List **actions)
+{
+ int prettyFlags = 0;
+ char *qualstr = TextDatumGetCString(ev_qual);
+ char *actionstr = TextDatumGetCString(ev_action);
+ List *actionNodeList = (List *) stringToNode(actionstr);
+ StringInfoData buf;
+
+ *whereClause = NULL;
+ *actions = NIL;
+ initStringInfo(&buf);
+
+ if (strcmp(qualstr, "<>") != 0)
+ {
+ Query *query = (Query *) linitial(actionNodeList);
+
+ pg_get_rule_whereclause(qualstr, query, &buf, 0);
+ *whereClause = pstrdup(buf.data);
+ }
+
+ if (list_length(actionNodeList) > 0)
+ {
+ ListCell *cell;
+
+ foreach(cell, actionNodeList)
+ {
+ Query *query = (Query *) lfirst(cell);
+
+ resetStringInfo(&buf);
+ get_query_def(query, &buf, NIL, NULL, true,
+ prettyFlags, WRAP_COLUMN_DEFAULT, 0);
+ *actions = lappend(*actions, pstrdup(buf.data));
+ }
+ }
+}
+
+/*
+ * To get the rewrite rule of a view when the CREATE VIEW command execution is
+ * still in progress: we search the system cache RULERELNAME to get the rewrite
+ * rule of the view as opposed to querying pg_rewrite as in pg_get_viewdef_worker(),
+ * which will return empty result.
+ */
+char *
+pg_get_viewdef_string(Oid viewoid)
+{
+ StringInfoData buf;
+ Relation pg_rewrite;
+ HeapTuple ruletup;
+ TupleDesc rulettc;
+
+ initStringInfo(&buf);
+ pg_rewrite = table_open(RewriteRelationId, AccessShareLock);
+
+ ruletup = SearchSysCache2(RULERELNAME,
+ ObjectIdGetDatum(viewoid),
+ PointerGetDatum(ViewSelectRuleName));
+ if (!HeapTupleIsValid(ruletup))
+ elog(ERROR, "cache lookup failed for rewrite rule for view with OID %u", viewoid);
+
+ rulettc = pg_rewrite->rd_att;
+ make_viewdef(&buf, ruletup, rulettc, 0, WRAP_COLUMN_DEFAULT);
+ ReleaseSysCache(ruletup);
+ table_close(pg_rewrite, AccessShareLock);
+
+ return buf.data;
+}
static char *
pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
@@ -1025,65 +1101,12 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
if (!isnull)
{
Node *qual;
- char relkind;
- deparse_context context;
- deparse_namespace dpns;
- RangeTblEntry *oldrte;
- RangeTblEntry *newrte;
-
- appendStringInfoString(&buf, "WHEN (");
+ char *qualstr;
qual = stringToNode(TextDatumGetCString(value));
+ qualstr = pg_get_trigger_whenclause(trigrec, qual, pretty);
- relkind = get_rel_relkind(trigrec->tgrelid);
-
- /* Build minimal OLD and NEW RTEs for the rel */
- oldrte = makeNode(RangeTblEntry);
- oldrte->rtekind = RTE_RELATION;
- oldrte->relid = trigrec->tgrelid;
- oldrte->relkind = relkind;
- oldrte->rellockmode = AccessShareLock;
- oldrte->alias = makeAlias("old", NIL);
- oldrte->eref = oldrte->alias;
- oldrte->lateral = false;
- oldrte->inh = false;
- oldrte->inFromCl = true;
-
- newrte = makeNode(RangeTblEntry);
- newrte->rtekind = RTE_RELATION;
- newrte->relid = trigrec->tgrelid;
- newrte->relkind = relkind;
- newrte->rellockmode = AccessShareLock;
- newrte->alias = makeAlias("new", NIL);
- newrte->eref = newrte->alias;
- newrte->lateral = false;
- newrte->inh = false;
- newrte->inFromCl = true;
-
- /* Build two-element rtable */
- memset(&dpns, 0, sizeof(dpns));
- dpns.rtable = list_make2(oldrte, newrte);
- dpns.subplans = NIL;
- dpns.ctes = NIL;
- dpns.appendrels = NULL;
- set_rtable_names(&dpns, NIL, NULL);
- set_simple_column_names(&dpns);
-
- /* Set up context with one-deep namespace stack */
- context.buf = &buf;
- context.namespaces = list_make1(&dpns);
- context.windowClause = NIL;
- context.windowTList = NIL;
- context.varprefix = true;
- context.prettyFlags = GET_PRETTY_FLAGS(pretty);
- context.wrapColumn = WRAP_COLUMN_DEFAULT;
- context.indentLevel = PRETTYINDENT_STD;
- context.special_exprkind = EXPR_KIND_NONE;
- context.appendparents = NULL;
-
- get_rule_expr(qual, &context, false);
-
- appendStringInfoString(&buf, ") ");
+ appendStringInfo(&buf, "WHEN (%s) ", qualstr);
}
appendStringInfo(&buf, "EXECUTE FUNCTION %s(",
@@ -1124,6 +1147,74 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
return buf.data;
}
+/*
+ * Pass back the TriggerWhen clause of a trigger given the pg_trigger record and
+ * the expression tree (in nodeToString() representation) from pg_trigger.tgqual
+ * for the trigger's WHEN condition.
+ */
+char *
+pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node *whenClause, bool pretty)
+{
+ StringInfoData buf;
+ char relkind;
+ deparse_context context;
+ deparse_namespace dpns;
+ RangeTblEntry *oldrte;
+ RangeTblEntry *newrte;
+
+ initStringInfo(&buf);
+
+ relkind = get_rel_relkind(trigrec->tgrelid);
+
+ /* Build minimal OLD and NEW RTEs for the rel */
+ oldrte = makeNode(RangeTblEntry);
+ oldrte->rtekind = RTE_RELATION;
+ oldrte->relid = trigrec->tgrelid;
+ oldrte->relkind = relkind;
+ oldrte->rellockmode = AccessShareLock;
+ oldrte->alias = makeAlias("old", NIL);
+ oldrte->eref = oldrte->alias;
+ oldrte->lateral = false;
+ oldrte->inh = false;
+ oldrte->inFromCl = true;
+
+ newrte = makeNode(RangeTblEntry);
+ newrte->rtekind = RTE_RELATION;
+ newrte->relid = trigrec->tgrelid;
+ newrte->relkind = relkind;
+ newrte->rellockmode = AccessShareLock;
+ newrte->alias = makeAlias("new", NIL);
+ newrte->eref = newrte->alias;
+ newrte->lateral = false;
+ newrte->inh = false;
+ newrte->inFromCl = true;
+
+ /* Build two-element rtable */
+ memset(&dpns, 0, sizeof(dpns));
+ dpns.rtable = list_make2(oldrte, newrte);
+ dpns.subplans = NIL;
+ dpns.ctes = NIL;
+ dpns.appendrels = NULL;
+ set_rtable_names(&dpns, NIL, NULL);
+ set_simple_column_names(&dpns);
+
+ /* Set up context with one-deep namespace stack */
+ context.buf = &buf;
+ context.namespaces = list_make1(&dpns);
+ context.windowClause = NIL;
+ context.windowTList = NIL;
+ context.varprefix = true;
+ context.prettyFlags = GET_PRETTY_FLAGS(pretty);
+ context.wrapColumn = WRAP_COLUMN_DEFAULT;
+ context.indentLevel = PRETTYINDENT_STD;
+ context.special_exprkind = EXPR_KIND_NONE;
+ context.appendparents = NULL;
+
+ get_rule_expr(whenClause, &context, false);
+
+ return buf.data;
+}
+
/* ----------
* pg_get_indexdef - Get the definition of an index
*
@@ -3486,7 +3577,12 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
-static void
+/*
+ * Produce the formatted SQL body (not the whole function definition)
+ * of a function given the pg_proc tuple. Save the formatted SQL in the
+ * given StringInfo.
+ */
+void
print_function_sqlbody(StringInfo buf, HeapTuple proctup)
{
int numargs;
@@ -5231,48 +5327,18 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
/* If the rule has an event qualification, add it */
if (strcmp(ev_qual, "<>") != 0)
{
- Node *qual;
- Query *query;
- deparse_context context;
- deparse_namespace dpns;
-
- if (prettyFlags & PRETTYFLAG_INDENT)
- appendStringInfoString(buf, "\n ");
- appendStringInfoString(buf, " WHERE ");
-
- qual = stringToNode(ev_qual);
-
/*
* We need to make a context for recognizing any Vars in the qual
* (which can only be references to OLD and NEW). Use the rtable of
* the first query in the action list for this purpose.
*/
- query = (Query *) linitial(actions);
-
- /*
- * If the action is INSERT...SELECT, OLD/NEW have been pushed down
- * into the SELECT, and that's what we need to look at. (Ugly kluge
- * ... try to fix this when we redesign querytrees.)
- */
- query = getInsertSelectQuery(query, NULL);
-
- /* Must acquire locks right away; see notes in get_query_def() */
- AcquireRewriteLocks(query, false, false);
-
- context.buf = buf;
- context.namespaces = list_make1(&dpns);
- context.windowClause = NIL;
- context.windowTList = NIL;
- context.varprefix = (list_length(query->rtable) != 1);
- context.prettyFlags = prettyFlags;
- context.wrapColumn = WRAP_COLUMN_DEFAULT;
- context.indentLevel = PRETTYINDENT_STD;
- context.special_exprkind = EXPR_KIND_NONE;
- context.appendparents = NULL;
+ Query *query = (Query *) linitial(actions);
- set_deparse_for_query(&dpns, query, NIL);
+ if (prettyFlags & PRETTYFLAG_INDENT)
+ appendStringInfoString(buf, "\n ");
+ appendStringInfoString(buf, " WHERE ");
- get_rule_expr(qual, &context, false);
+ pg_get_rule_whereclause(ev_qual, query, buf, prettyFlags);
}
appendStringInfoString(buf, " DO ");
@@ -5313,6 +5379,47 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
table_close(ev_relation, AccessShareLock);
}
+/*
+ * Given a string corresponding to a rule's pg_rewrite.ev_qual and a query
+ * parsetree, append ev_qual's text representation into the output buf.
+ *
+ * Tries to pretty up the output according to prettyFlags.
+ */
+static void
+pg_get_rule_whereclause(char *qualstr, Query *query, StringInfo buf,
+ int prettyFlags)
+{
+ Node *qual;
+ deparse_context context;
+ deparse_namespace dpns;
+
+ qual = stringToNode(qualstr);
+
+ /*
+ * If the action is INSERT...SELECT, OLD/NEW have been pushed down
+ * into the SELECT, and that's what we need to look at. (Ugly kluge
+ * ... try to fix this when we redesign querytrees.)
+ */
+ query = getInsertSelectQuery(query, NULL);
+
+ /* Must acquire locks right away; see notes in get_query_def() */
+ AcquireRewriteLocks(query, false, false);
+
+ context.buf = buf;
+ context.namespaces = list_make1(&dpns);
+ context.windowClause = NIL;
+ context.windowTList = NIL;
+ context.varprefix = (list_length(query->rtable) != 1);
+ context.prettyFlags = prettyFlags;
+ context.wrapColumn = WRAP_COLUMN_DEFAULT;
+ context.indentLevel = PRETTYINDENT_STD;
+ context.special_exprkind = EXPR_KIND_NONE;
+ context.appendparents = NULL;
+
+ set_deparse_for_query(&dpns, query, NIL);
+
+ get_rule_expr(qual, &context, false);
+}
/* ----------
* make_viewdef - reconstruct the SELECT part of a
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 12259c9dd8..ce5f9b4112 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5722,6 +5722,7 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
pubdesc->pubactions.pubtruncate |= pubform->pubtruncate;
pubdesc->pubactions.pubddl_table |= pubform->pubddl_table;
pubdesc->pubactions.pubddl_index |= pubform->pubddl_index;
+ pubdesc->pubactions.pubddl_all |= pubform->pubddl_all;
/*
* Check if all columns referenced in the filter expression are part
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index fdcf27dd1e..1623abf145 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4067,6 +4067,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubtruncate;
int i_pubddl_table;
int i_pubddl_index;
+ int i_pubddl_all;
int i_pubviaroot;
int i,
ntups;
@@ -4086,25 +4087,25 @@ getPublications(Archive *fout, int *numPublications)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubddl_table, p.pubddl_index, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubddl_table, p.pubddl_index, p.pubddl_all, p.pubviaroot "
"FROM pg_publication p");
else if (fout->remoteVersion >= 130000)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false as p.pubddl_all, p.pubviaroot "
"FROM pg_publication p");
else if (fout->remoteVersion >= 110000)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false AS pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false as p.pubddl_all, false AS pubviaroot "
"FROM pg_publication p");
else
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false AS pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false as p.pubddl_table, false as p.pubddl_index, false as p.pubddl_all, false AS pubviaroot "
"FROM pg_publication p");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4122,6 +4123,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubtruncate = PQfnumber(res, "pubtruncate");
i_pubddl_table = PQfnumber(res, "pubddl_table");
i_pubddl_index = PQfnumber(res, "pubddl_index");
+ i_pubddl_all = PQfnumber(res, "pubddl_all");
i_pubviaroot = PQfnumber(res, "pubviaroot");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4149,6 +4151,8 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubddl_table), "t") == 0);
pubinfo[i].pubddl_index =
(strcmp(PQgetvalue(res, i, i_pubddl_index), "t") == 0);
+ pubinfo[i].pubddl_all =
+ (strcmp(PQgetvalue(res, i, i_pubddl_all), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
@@ -4250,6 +4254,15 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
first = false;
}
+ if (pubinfo->pubddl_all)
+ {
+ if (!first)
+ appendPQExpBufferStr(query, ", ");
+
+ appendPQExpBufferStr(query, "all");
+ first = false;
+ }
+
appendPQExpBufferStr(query, "'");
}
@@ -8040,7 +8053,7 @@ getPublicationEventTriggers(Archive *fout, SimpleStringList *skipTriggers)
appendPQExpBufferStr(query,
"SELECT oid FROM pg_publication "
- "WHERE pubddl_table");
+ "WHERE pubddl_all OR pubddl_table");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
ntups = PQntuples(res);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index efd63477a8..706f206c8b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -622,6 +622,7 @@ typedef struct _PublicationInfo
bool pubtruncate;
bool pubddl_table;
bool pubddl_index;
+ bool pubddl_all;
bool pubviaroot;
} PublicationInfo;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index df79c79e8d..568fe296b3 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6208,9 +6208,11 @@ listPublications(const char *pattern)
{
appendPQExpBuffer(&buf,
",\n pubddl_table AS \"%s\",\n"
- " pubddl_index AS \"%s\"\n",
+ " pubddl_index AS \"%s\",\n"
+ " pubddl_all AS \"%s\"\n",
gettext_noop("Table DDLs"),
- gettext_noop("Index DDLs"));
+ gettext_noop("Index DDLs"),
+ gettext_noop("All DDLs"));
}
appendPQExpBuffer(&buf,
",\n pubinsert AS \"%s\",\n"
@@ -6343,7 +6345,7 @@ describePublications(const char *pattern)
" puballtables");
if (has_pubddl)
appendPQExpBufferStr(&buf,
- ", pubddl_table, pubddl_index");
+ ", pubddl_table, pubddl_index, pubddl_all");
appendPQExpBufferStr(&buf,
", pubinsert, pubupdate, pubdelete");
@@ -6405,7 +6407,7 @@ describePublications(const char *pattern)
if (has_pubviaroot)
ncols++;
if (has_pubddl)
- ncols += 2;
+ ncols += 3;
initPQExpBuffer(&title);
printfPQExpBuffer(&title, _("Publication %s"), pubname);
@@ -6417,6 +6419,7 @@ describePublications(const char *pattern)
{
printTableAddHeader(&cont, gettext_noop("Table DDLs"), true, align);
printTableAddHeader(&cont, gettext_noop("Index DDLs"), true, align);
+ printTableAddHeader(&cont, gettext_noop("All DDLs"), true, align);
}
printTableAddHeader(&cont, gettext_noop("Inserts"), true, align);
printTableAddHeader(&cont, gettext_noop("Updates"), true, align);
@@ -6439,6 +6442,7 @@ describePublications(const char *pattern)
{
printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
+ printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
}
if (!puballtables)
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 467731c61a..35b3ea25e5 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -69,6 +69,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
/* true if index ddls are published */
bool pubddl_index;
+
+ /* true if all supported ddls are published */
+ bool pubddl_all;
} FormData_pg_publication;
/* ----------------
@@ -89,6 +92,7 @@ typedef struct PublicationActions
bool pubtruncate;
bool pubddl_table;
bool pubddl_index;
+ bool pubddl_all;
} PublicationActions;
typedef struct PublicationDesc
diff --git a/src/include/commands/collationcmds.h b/src/include/commands/collationcmds.h
index b76c7b3dc3..53c4a1a8c2 100644
--- a/src/include/commands/collationcmds.h
+++ b/src/include/commands/collationcmds.h
@@ -18,7 +18,8 @@
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
-extern ObjectAddress DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_exists);
+extern ObjectAddress DefineCollation(ParseState *pstate, List *names, List *parameters,
+ bool if_not_exists, ObjectAddress *from_collid);
extern void IsThereCollationInNamespace(const char *collname, Oid nspOid);
extern ObjectAddress AlterCollation(AlterCollationStmt *stmt);
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index cba4e72455..339553d456 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -132,5 +132,7 @@ extern void EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt,
extern void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt,
Oid cfgId, Oid *dictIds, int ndicts);
extern void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
+extern void EventTriggerCollectSecLabel(ObjectAddress address, char *provider,
+ SecLabelStmt *stmt);
#endif /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h
index 076c27e642..0259a4b4f6 100644
--- a/src/include/tcop/cmdtag.h
+++ b/src/include/tcop/cmdtag.h
@@ -53,7 +53,7 @@ CopyQueryCompletion(QueryCompletion *dst, const QueryCompletion *src)
extern void InitializeQueryCompletion(QueryCompletion *qc);
extern const char *GetCommandTagName(CommandTag commandTag);
extern const char *GetCommandTagNameAndLen(CommandTag commandTag, Size *len);
-extern CommandTag *GetCommandTagsForDDLRepl(int *ncommands);
+extern List *GetCommandTagsForDDLRepl(void);
extern bool command_tag_display_rowcount(CommandTag commandTag);
extern bool command_tag_event_trigger_ok(CommandTag commandTag);
extern bool command_tag_table_rewrite_ok(CommandTag commandTag);
diff --git a/src/include/tcop/ddl_deparse.h b/src/include/tcop/ddl_deparse.h
index 2b7754bfac..581a470c09 100644
--- a/src/include/tcop/ddl_deparse.h
+++ b/src/include/tcop/ddl_deparse.h
@@ -12,11 +12,13 @@
#ifndef DDL_DEPARSE_H
#define DDL_DEPARSE_H
+#include "commands/event_trigger.h"
#include "tcop/deparse_utility.h"
extern char *deparse_utility_command(CollectedCommand *cmd, bool include_owner, bool verbose_mode);
extern char *deparse_ddl_json_to_string(char *jsonb, char** owner);
extern char *deparse_drop_command(const char *objidentity, const char *objecttype,
DropBehavior behavior);
+extern char * deparse_AlterPublicationDropStmt(SQLDropObject *obj);
#endif /* DDL_DEPARSE_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 87a761bb3e..cce3c07fc0 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -30,6 +30,7 @@ typedef enum CollectedCommandType
SCT_AlterDefaultPrivileges,
SCT_CreateOpClass,
SCT_AlterTSConfig,
+ SCT_SecurityLabel,
SCT_CreateTableAs
} CollectedCommandType;
@@ -105,6 +106,13 @@ typedef struct CollectedCommand
ObjectType objtype;
} defprivs;
+ /* SECURITY LABEL */
+ struct
+ {
+ ObjectAddress address;
+ char *provider;
+ } seclabel;
+
/* CREATE TABLE AS */
struct
{
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index f8e1238fa2..f05578d322 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -209,6 +209,8 @@ extern AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId,
AclMode mask, AclMaskHow how);
extern int aclmembers(const Acl *acl, Oid **roleids);
+extern const char *privilege_to_string(AclMode privilege);
+
extern bool has_privs_of_role(Oid member, Oid role);
extern bool member_can_set_role(Oid member, Oid role);
extern void check_can_set_role(Oid member, Oid role);
diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h
index 55af624fb3..946545f53f 100644
--- a/src/include/utils/aclchk_internal.h
+++ b/src/include/utils/aclchk_internal.h
@@ -39,6 +39,7 @@ typedef struct
List *grantees;
bool grant_option;
DropBehavior behavior;
+ Oid grantor_uid;
} InternalGrant;
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index c90c32bff1..48b8bfd79a 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -123,6 +123,7 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
#define FORMAT_TYPE_FORCE_QUALIFY 0x04 /* force qualification of type */
#define FORMAT_TYPE_INVALID_AS_NULL 0x08 /* NULL if undefined */
extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
+extern char *format_procedure_args(Oid procedure_oid, bool force_qualify);
extern char *format_type_be(Oid type_oid);
extern char *format_type_be_qualified(Oid type_oid);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 9adc589173..b6e20e7ee4 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -13,9 +13,12 @@
#ifndef RULEUTILS_H
#define RULEUTILS_H
+#include "access/htup.h"
+#include "catalog/pg_trigger.h"
#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;
@@ -23,7 +26,10 @@ struct PlannedStmt;
extern char *pg_get_indexdef_string(Oid indexrelid);
extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
+extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
+ Node *whenClause, bool pretty);
extern char *pg_get_querydef(Query *query, bool pretty);
+extern char *pg_get_viewdef_string(Oid viewoid);
extern char *pg_get_partkeydef_columns(Oid relid, bool pretty);
extern char *pg_get_partkeydef_string(Oid relid);
@@ -31,6 +37,9 @@ 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 void pg_get_ruledef_detailed(Datum ev_qual, Datum ev_action,
+ char **whereClause, List **actions);
+
extern char *deparse_expression(Node *expr, List *dpcontext,
bool forceprefix, bool showimplicit);
extern List *deparse_context_for(const char *aliasname, Oid relid);
@@ -52,5 +61,6 @@ extern void get_opclass_name(Oid opclass, Oid actual_datatype,
extern char *flatten_reloptions(Oid relid);
extern char *pg_get_statisticsobjdef_string(Oid statextid);
+extern void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
#endif /* RULEUTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 3e6d2791c8..4474c3b730 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -6223,9 +6223,9 @@ List of schemas
(0 rows)
\dRp "no.such.publication"
- List of publications
- Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
-------+-------+------------+------------+------------+---------+---------+---------+-----------+----------
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+------+-------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
(0 rows)
\dRs "no.such.subscription"
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index ebcff41c0c..c54ce53e7b 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -30,20 +30,20 @@ ERROR: conflicting or redundant options
LINE 1: ...ub_xxx WITH (publish_via_partition_root = 'true', publish_vi...
^
\dRp
- List of publications
- Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f | f | f | t | f | f | f | f
- testpub_default | regress_publication_user | f | f | f | f | t | f | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | f | f | f | t | f | f | f | f
+ testpub_default | regress_publication_user | f | f | f | f | f | t | f | f | f
(2 rows)
ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
\dRp
- List of publications
- Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f | f | f | t | f | f | f | f
- testpub_default | regress_publication_user | f | f | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | f | f | f | t | f | f | f | f
+ testpub_default | regress_publication_user | f | f | f | f | t | t | t | f | f
(2 rows)
--- adding tables
@@ -87,10 +87,10 @@ RESET client_min_messages;
-- should be able to add schema to 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable ADD TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl1"
Tables from schemas:
@@ -99,20 +99,20 @@ Tables from schemas:
-- should be able to drop schema from 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable DROP TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl1"
-- should be able to set schema to 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test"
@@ -123,10 +123,10 @@ CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test;
CREATE PUBLICATION testpub_for_tbl_schema FOR TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
RESET client_min_messages;
\dRp+ testpub_for_tbl_schema
- Publication testpub_for_tbl_schema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_for_tbl_schema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
Tables from schemas:
@@ -135,10 +135,10 @@ Tables from schemas:
-- should be able to add a table of the same schema to the schema publication
ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
Tables from schemas:
@@ -147,10 +147,10 @@ Tables from schemas:
-- should be able to drop the table
ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test"
@@ -161,10 +161,10 @@ ERROR: relation "testpub_nopk" is not part of the publication
-- should be able to set table to schema publication
ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
@@ -186,10 +186,10 @@ Publications:
"testpub_foralltables"
\dRp+ testpub_foralltables
- Publication testpub_foralltables
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | t | f | f | t | t | f | f | f
+ Publication testpub_foralltables
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | t | f | f | f | t | t | f | f | f
(1 row)
DROP TABLE testpub_tbl2;
@@ -201,19 +201,19 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3;
CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3;
RESET client_min_messages;
\dRp+ testpub3
- Publication testpub3
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub3
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl3"
"public.testpub_tbl3a"
\dRp+ testpub4
- Publication testpub4
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub4
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl3"
@@ -234,10 +234,10 @@ UPDATE testpub_parted1 SET a = 1;
-- only parent is listed as being in publication, not the partition
ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted;
\dRp+ testpub_forparted
- Publication testpub_forparted
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_forparted
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"public.testpub_parted"
@@ -252,10 +252,10 @@ ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
UPDATE testpub_parted1 SET a = 1;
ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true);
\dRp+ testpub_forparted
- Publication testpub_forparted
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | t
+ Publication testpub_forparted
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | t
Tables:
"public.testpub_parted"
@@ -284,10 +284,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub5 FOR TABLE testpub_rf_tbl1, testpub_rf_tbl2 WHERE (c <> 'test' AND d < 5) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -300,10 +300,10 @@ Tables:
ALTER PUBLICATION testpub5 ADD TABLE testpub_rf_tbl3 WHERE (e > 1000 AND e < 2000);
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -319,10 +319,10 @@ Publications:
ALTER PUBLICATION testpub5 DROP TABLE testpub_rf_tbl2;
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl3" WHERE ((e > 1000) AND (e < 2000))
@@ -330,10 +330,10 @@ Tables:
-- remove testpub_rf_tbl1 and add testpub_rf_tbl3 again (another WHERE expression)
ALTER PUBLICATION testpub5 SET TABLE testpub_rf_tbl3 WHERE (e > 300 AND e < 500);
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl3" WHERE ((e > 300) AND (e < 500))
@@ -366,10 +366,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_syntax1 FOR TABLE testpub_rf_tbl1, ONLY testpub_rf_tbl3 WHERE (e < 999) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub_syntax1
- Publication testpub_syntax1
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | f | f | f | f
+ Publication testpub_syntax1
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl3" WHERE (e < 999)
@@ -379,10 +379,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_syntax2 FOR TABLE testpub_rf_tbl1, testpub_rf_schema1.testpub_rf_tbl5 WHERE (h < 999) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub_syntax2
- Publication testpub_syntax2
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | f | f | f | f
+ Publication testpub_syntax2
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"testpub_rf_schema1.testpub_rf_tbl5" WHERE (h < 999)
@@ -497,10 +497,10 @@ CREATE PUBLICATION testpub6 FOR TABLES IN SCHEMA testpub_rf_schema2;
ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpub_rf_schema2.testpub_rf_tbl6 WHERE (i < 99);
RESET client_min_messages;
\dRp+ testpub6
- Publication testpub6
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub6
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"testpub_rf_schema2.testpub_rf_tbl6" WHERE (i < 99)
Tables from schemas:
@@ -714,10 +714,10 @@ CREATE PUBLICATION testpub_table_ins WITH (publish = 'insert, truncate');
RESET client_min_messages;
ALTER PUBLICATION testpub_table_ins ADD TABLE testpub_tbl5 (a); -- ok
\dRp+ testpub_table_ins
- Publication testpub_table_ins
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | f | f | t | f
+ Publication testpub_table_ins
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | f | f | t | f
Tables:
"public.testpub_tbl5" (a)
@@ -891,10 +891,10 @@ CREATE TABLE testpub_tbl_both_filters (a int, b int, c int, PRIMARY KEY (a,c));
ALTER TABLE testpub_tbl_both_filters REPLICA IDENTITY USING INDEX testpub_tbl_both_filters_pkey;
ALTER PUBLICATION testpub_both_filters ADD TABLE testpub_tbl_both_filters (a,c) WHERE (c != 1);
\dRp+ testpub_both_filters
- Publication testpub_both_filters
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_both_filters
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"public.testpub_tbl_both_filters" (a, c) WHERE (c <> 1)
@@ -1099,10 +1099,10 @@ ERROR: relation "testpub_tbl1" is already member of publication "testpub_fortbl
CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1;
ERROR: publication "testpub_fortbl" already exists
\dRp+ testpub_fortbl
- Publication testpub_fortbl
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_fortbl
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -1140,10 +1140,10 @@ Publications:
"testpub_fortbl"
\dRp+ testpub_default
- Publication testpub_default
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | f | f
+ Publication testpub_default
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | f | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -1221,10 +1221,10 @@ REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
- Publication testpub_default
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | f | f
+ Publication testpub_default
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | f | f
(1 row)
-- fail - must be owner of publication
@@ -1234,20 +1234,20 @@ ERROR: must be owner of publication testpub_default
RESET ROLE;
ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
\dRp testpub_foo
- List of publications
- Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
--------------+--------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- testpub_foo | regress_publication_user | f | f | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+-------------+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ testpub_foo | regress_publication_user | f | f | f | f | t | t | t | f | f
(1 row)
-- rename back to keep the rest simple
ALTER PUBLICATION testpub_foo RENAME TO testpub_default;
ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
- List of publications
- Name | Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
------------------+---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- testpub_default | regress_publication_user2 | f | f | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+-----------------+---------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ testpub_default | regress_publication_user2 | f | f | f | f | t | t | t | f | f
(1 row)
-- adding schemas and tables
@@ -1263,19 +1263,19 @@ CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub1_forschema FOR TABLES IN SCHEMA pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
CREATE PUBLICATION testpub2_forschema FOR TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1289,44 +1289,44 @@ CREATE PUBLICATION testpub6_forschema FOR TABLES IN SCHEMA "CURRENT_SCHEMA", CUR
CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
RESET client_min_messages;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"public"
\dRp+ testpub4_forschema
- Publication testpub4_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub4_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"CURRENT_SCHEMA"
\dRp+ testpub5_forschema
- Publication testpub5_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub5_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"CURRENT_SCHEMA"
"public"
\dRp+ testpub6_forschema
- Publication testpub6_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub6_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"CURRENT_SCHEMA"
"public"
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"CURRENT_SCHEMA.CURRENT_SCHEMA"
@@ -1360,10 +1360,10 @@ ERROR: schema "testpub_view" does not exist
-- dropping the schema should reflect the change in publication
DROP SCHEMA pub_test3;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1371,20 +1371,20 @@ Tables from schemas:
-- renaming the schema should reflect the change in publication
ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1_renamed"
"pub_test2"
ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1392,10 +1392,10 @@ Tables from schemas:
-- alter publication add schema
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1404,10 +1404,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1416,10 +1416,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test1;
ERROR: schema "pub_test1" is already member of publication "testpub1_forschema"
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1427,10 +1427,10 @@ Tables from schemas:
-- alter publication drop schema
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1438,10 +1438,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
ERROR: tables from schema "pub_test2" are not part of the publication
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1449,29 +1449,29 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
-- drop all schemas
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
(1 row)
-- alter publication set multiple schema
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1480,10 +1480,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1492,10 +1492,10 @@ Tables from schemas:
-- removing the duplicate schemas
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1574,18 +1574,18 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub3_forschema;
RESET client_min_messages;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
(1 row)
ALTER PUBLICATION testpub3_forschema SET TABLES IN SCHEMA pub_test1;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables from schemas:
"pub_test1"
@@ -1595,20 +1595,20 @@ CREATE PUBLICATION testpub_forschema_fortable FOR TABLES IN SCHEMA pub_test1, TA
CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, TABLES IN SCHEMA pub_test1;
RESET client_min_messages;
\dRp+ testpub_forschema_fortable
- Publication testpub_forschema_fortable
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_forschema_fortable
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"pub_test2.tbl1"
Tables from schemas:
"pub_test1"
\dRp+ testpub_fortable_forschema
- Publication testpub_fortable_forschema
- Owner | All tables | Table DDLs | Index DDLs | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | f | f | t | t | t | t | f
+ Publication testpub_fortable_forschema
+ Owner | All tables | Table DDLs | Index DDLs | All DDLs | Inserts | Updates | Deletes | Truncates | Via root
+--------------------------+------------+------------+------------+----------+---------+---------+---------+-----------+----------
+ regress_publication_user | f | f | f | f | t | t | t | t | f
Tables:
"pub_test2.tbl1"
Tables from schemas:
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 9c52890f1d..c2bd4d3f62 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -115,18 +115,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
\dRs+ regress_testsub4
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | none | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | none | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
\dRs+ regress_testsub4
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
DROP SUBSCRIPTION regress_testsub3;
@@ -144,10 +144,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
ERROR: invalid connection string syntax: missing "=" after "foobar" in connection info string
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -166,10 +166,10 @@ ERROR: unrecognized subscription parameter: "create_slot"
-- ok
ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | off | dbname=regress_doesnotexist2 | 0/12345
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist2 | 0/12345
(1 row)
-- ok - with lsn = NONE
@@ -178,10 +178,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
ERROR: invalid WAL location (LSN): 0/0
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | off | dbname=regress_doesnotexist2 | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist2 | 0/0
(1 row)
BEGIN;
@@ -213,10 +213,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
ERROR: invalid value for parameter "synchronous_commit": "foobar"
HINT: Available values: local, remote_write, remote_apply, on, off.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | local | dbname=regress_doesnotexist2 | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | t | local | dbname=regress_doesnotexist2 | 0/0
(1 row)
-- rename back to keep the rest simple
@@ -245,19 +245,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | t | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | t | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (binary = false);
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
DROP SUBSCRIPTION regress_testsub;
@@ -269,27 +269,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | on | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | on | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
-- fail - publication already exists
@@ -304,10 +304,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
ERROR: publication "testpub1" is already in subscription "regress_testsub"
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub,testpub1,testpub2} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub,testpub1,testpub2} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
-- fail - publication used more than once
@@ -322,10 +322,10 @@ ERROR: publication "testpub3" is not in subscription "regress_testsub"
-- ok - delete publications
ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
DROP SUBSCRIPTION regress_testsub;
@@ -361,10 +361,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | p | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | p | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
--fail - alter of two_phase option not supported.
@@ -373,10 +373,10 @@ ERROR: unrecognized subscription parameter: "two_phase"
-- but can alter streaming when two_phase enabled
ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -386,10 +386,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -402,18 +402,18 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | t | any | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Run as Owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | t | any | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
--
2.30.0.windows.2
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, shveta.malik@gmail.com, vignesh21@gmail.com, amit.kapila16@gmail.com, itsajin@gmail.com, wangw.fnst@fujitsu.com, runqidev@gmail.com, smithpb2250@gmail.com, tgl@sss.pgh.pa.us, ggysxcq@gmail.com, dilipbalaut@gmail.com, alvherre@alvh.no-ip.org, sawada.mshk@gmail.com, 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: <OS0PR01MB571643602A8B7226B558DBAA94969@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