public inbox for pgsql-general@postgresql.org
help / color / mirror / Atom feedFrom: shveta malik <shveta.malik@gmail.com>
To: Amit Kapila <amit.kapila16@gmail.com>
Cc: Michael Paquier <michael@paquier.xyz>
Cc: Wei Wang (Fujitsu) <wangw.fnst@fujitsu.com>
Cc: Yu Shi (Fujitsu) <shiy.fnst@fujitsu.com>
Cc: vignesh C <vignesh21@gmail.com>
Cc: Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com>
Cc: Ajin Cherian <itsajin@gmail.com>
Cc: Runqi Tian <runqidev@gmail.com>
Cc: Peter Smith <smithpb2250@gmail.com>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Cc: li jie <ggysxcq@gmail.com>
Cc: Dilip Kumar <dilipbalaut@gmail.com>
Cc: Alvaro Herrera <alvherre@alvh.no-ip.org>
Cc: 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>
Cc: shveta malik <shveta.malik@gmail.com>
Subject: Re: Support logical replication of DDLs
Date: Thu, 22 Jun 2023 09:39:30 +0530
Message-ID: <CAJpy0uDLLBYAOzCePYObZ51k1epBU0hef4vbfcujKJprJVsEcQ@mail.gmail.com> (raw)
In-Reply-To: <CAA4eK1+K8KMsB=+jJO6wDUSt7wF1RiXKtF-HN48nCOEOv-J-3Q@mail.gmail.com>
References: <OSZPR01MB63102C42A24D59FACF6D9CD6FD4A9@OSZPR01MB6310.jpnprd01.prod.outlook.com>
<CAJpy0uCbwqWj+p_yj1AHyiufzAUv_H29qOaztAXxFoTqZ9WcAw@mail.gmail.com>
<OSZPR01MB6310E0509F229AF2A9414BB1FD499@OSZPR01MB6310.jpnprd01.prod.outlook.com>
<CAJpy0uBwCZCniPR6vh26L+wpSf4xzUN8omUa9DzF-x1CAud_xA@mail.gmail.com>
<CAA4eK1LOr+2O+_pWKTaa0y9vbW6whfm-8-fuBvnS6OBiaR+7TA@mail.gmail.com>
<CAA4eK1LF3EaCSj5iqO0oT1k3ew7YnQbbKEgbzORDAdvdtd+r7w@mail.gmail.com>
<CAJpy0uDtrxPo127LH3FP-TffynrspPFqhhC7o_GFOMP+2mPtWQ@mail.gmail.com>
<OS3PR01MB6275328379FBE5734B4585619E54A@OS3PR01MB6275.jpnprd01.prod.outlook.com>
<ZIgf3qBvFoTsMhIc@paquier.xyz>
<CAA4eK1J8WOK9VZ9RpQRNRhRYhFOKQCu=GLCu+iBOePNO3JwLbQ@mail.gmail.com>
<ZIjmGhPWdoJUfjjE@paquier.xyz>
<CAJpy0uDaubBHyqPc1k0OysuBYDOVdoUgTWG4jXDCYj-OVSU8hg@mail.gmail.com>
<CAA4eK1+K8KMsB=+jJO6wDUSt7wF1RiXKtF-HN48nCOEOv-J-3Q@mail.gmail.com>
On Mon, Jun 19, 2023 at 3:04 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> On Fri, Jun 16, 2023 at 4:01 PM shveta malik <shveta.malik@gmail.com> wrote:
> >
> > With these changes, I hope the patch-set is somewhat easier to review.
> >
>
> Few comments:
> =============
> 1.
> +static Jsonb *
> +deparse_CreateStmt(Oid objectId, Node *parsetree)
> {
> ...
> + /* PERSISTENCE */
> + appendStringInfoString(&fmtStr, "CREATE %{persistence}s TABLE");
> + new_jsonb_VA(state, NULL, NULL, false, 1,
> + "persistence", jbvString,
> + get_persistence_str(relation->rd_rel->relpersistence));
>
> Do we need to add key/value pair if get_persistence_str() returns an
> empty string in the default deparsing mode? Won't it be somewhat
> inconsistent with other objects?
>
Modified it to add 'persistence' only when we have it non-null.
> 2.
> +static JsonbValue *
> +new_jsonb_VA(JsonbParseState *state, char *parentKey, char *fmt,
> + bool createChildObj, int numobjs,...)
> +{
> + va_list args;
> + int i;
> + JsonbValue val;
> + JsonbValue *value = NULL;
> +
> + /*
> + * Insert parent key for which we are going to create value object here.
> + */
> + if (parentKey)
> + insert_jsonb_key(state, parentKey);
> +
> + /* Set up the toplevel object if not instructed otherwise */
> + if (createChildObj)
> + pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
> +
> + /* Set up the "fmt" */
> + if (fmt)
> + fmt_to_jsonb_element(state, fmt);
>
> I think it would be better to handle parentKey, childObj, and fmt in
> the callers as this function doesn't seem to be the ideal place to
> deal with those. I see that in some cases we already handle those in
> the callers. It is bit confusing in which case callers need to deal
> vs. the cases where we need to deal here.
>
Moved these things outside of new_jsonb_VA().
> 3.
> +static Jsonb *
> +deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
> {
> ...
> +
> + new_jsonb_VA(state, NULL, "ALTER SEQUENCE %{identity}D %{definition: }s",
> + false, 0);
>
> Is there a need to call new_jsonb_VA() just to insert format? Won't it
> better to do this in the caller itself?
>
Now in the latest version, "fmt" is inserted as a normal key-value
pair only, no special handling for this. And thus above call is
retained but with numObjs as 1.
> 4. The handling for if_not_exists appears to be different in
> deparse_CreateSeqStmt() and deparse_CreateStmt(). I think the later
> one is correct and we should do that in both places. That means
> probably we can't have the entire format key in the beginning of
> deparse_CreateSeqStmt().
>
Modified.
> 5.
> + /*
> + * Check if table elements are present, if so, add them. This function
> + * call takes care of both the check and addition.
> + */
> + telems = insert_table_elements(state, &fmtStr, relation,
> + node->tableElts, dpcontext, objectId,
> + false, /* not typed table */
> + false); /* not composite */
>
> Would it be better to name this function to something like
> add_table_elems_if_any()? If so, we can remove second part of the
> comment: "This function call takes care of both the check and
> addition." as that would be obvious from the function name.
>
Modified.
> 6.
> + /*
> + * Check if table elements are present, if so, add them. This function
> + * call takes care of both the check and addition.
> + */
> + telems = insert_table_elements(state, &fmtStr, relation,
> + node->tableElts, dpcontext, objectId,
> + false, /* not typed table */
> + false); /* not composite */
> +
> + /*
> + * If no table elements added, then add empty "()" needed for 'inherit'
> + * create table syntax. Example: CREATE TABLE t1 () INHERITS (t0);
> + */
> + if (!telems)
> + appendStringInfoString(&fmtStr, " ()");
>
> In insert_table_elements(), sometimes we access system twice for each
> of the columns and this is to identify the above case where no
> elements are present. Would it be better if simply for zero element
> object array in this case and detect the same on the receiving side?
> If this is feasible then we can simply name the function as
> add_table_elems/add_table_elements. Also, in this context, can we
> change the variable name telems to telems_present to make it bit easy
> to follow.
Modified telems to telems_present. I am reviewing the first part.
Please allow some more time.
>
> 7. It would be better if we can further split the patch to move Alter
> case into a separate patch. That will help us to focus on reviewing
> Create/Drop in detail.
>
Done. Alter-table deparsing is now patch 0002.
======
Apart from above, did some more optimization on similar lines (i.e.
add elements only if needed) and added 'syntax' related comments for
each alter-table subcmd.
thanks
Shveta
Attachments:
[application/octet-stream] 0002-Deparser-for-Alter-Table-DDL-commands-2023_06_22.patch (59.1K, 2-0002-Deparser-for-Alter-Table-DDL-commands-2023_06_22.patch)
download | inline diff:
From 83093f6190cb83f20a52a597cf54c3d5edc6471f Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Tue, 20 Jun 2023 17:15:21 +0530
Subject: [PATCH 2/5] Deparser for Alter Table DDL commands.
This patch constructs JSON blobs representing the Alter Table
DDL commands which can later be re-processed into plain strings
by well-defined sprintf-like expansion.
---
src/backend/commands/ddldeparse.c | 1719 ++++++++++++++++++++++++++++-
src/backend/tcop/utility.c | 17 +
src/include/tcop/utility.h | 2 +
3 files changed, 1699 insertions(+), 39 deletions(-)
diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c
index 269880fbb9..faaf0de3f9 100644
--- a/src/backend/commands/ddldeparse.c
+++ b/src/backend/commands/ddldeparse.c
@@ -410,14 +410,21 @@ insert_identity_object(JsonbParseState *state, Oid nspid, char *relname)
* Deparse the sequence CACHE option to Jsonb
*
* Verbose syntax
+ * SET CACHE %{value}s
+ * OR
* CACHE %{value}
*/
static inline void
-deparse_Seq_Cache_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata)
+deparse_Seq_Cache_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata,
+ bool alter_table)
{
+ char *fmt;
+
+ fmt = alter_table ? "SET CACHE %{value}s" : "CACHE %{value}s";
+
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 3,
- "fmt", jbvString, "CACHE %{value}s",
+ "fmt", jbvString, fmt,
"clause", jbvString, "cache",
"value", jbvString,
psprintf(INT64_FORMAT, seqdata->seqcache));
@@ -428,14 +435,21 @@ deparse_Seq_Cache_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata)
* Deparse the sequence CYCLE option to Jsonb.
*
* Verbose syntax
+ * SET %{no}s CYCLE
+ * OR
* %{no}s CYCLE
*/
static inline void
-deparse_Seq_Cycle_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata)
+deparse_Seq_Cycle_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata,
+ bool alter_table)
{
+ char *fmt;
+
+ fmt = alter_table ? "SET %{no}s CYCLE" : "%{no}s CYCLE";
+
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 3,
- "fmt", jbvString, "%{no}s CYCLE",
+ "fmt", jbvString, fmt,
"clause", jbvString, "cycle",
"no", jbvString, seqdata->seqcycle ?
"" : "NO");
@@ -446,15 +460,22 @@ deparse_Seq_Cycle_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata)
* Deparse the sequence INCREMENT BY option to Jsonb
*
* Verbose syntax
+ * SET INCREMENT BY %{value}s
+ * OR
* INCREMENT BY %{value}s
*/
static inline void
deparse_Seq_IncrementBy_toJsonb(JsonbParseState *state,
- Form_pg_sequence seqdata)
+ Form_pg_sequence seqdata, bool alter_table)
{
+ char *fmt;
+
+ fmt = alter_table ? "SET INCREMENT BY %{value}s"
+ : "INCREMENT BY %{value}s";
+
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 3,
- "fmt", jbvString, "INCREMENT BY %{value}s",
+ "fmt", jbvString, fmt,
"clause", jbvString, "seqincrement",
"value", jbvString,
psprintf(INT64_FORMAT, seqdata->seqincrement));
@@ -465,15 +486,21 @@ deparse_Seq_IncrementBy_toJsonb(JsonbParseState *state,
* Deparse the sequence MAXVALUE option to Jsonb.
*
* Verbose syntax
+ * SET MAXVALUE %{value}s
+ * OR
* MAXVALUE %{value}s
*/
static inline void
deparse_Seq_Maxvalue_toJsonb(JsonbParseState *state,
- Form_pg_sequence seqdata)
+ Form_pg_sequence seqdata, bool alter_table)
{
+ char *fmt;
+
+ fmt = alter_table ? "SET MAXVALUE %{value}s" : "MAXVALUE %{value}s";
+
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 3,
- "fmt", jbvString, "MAXVALUE %{value}s",
+ "fmt", jbvString, fmt,
"clause", jbvString, "maxvalue",
"value", jbvString,
psprintf(INT64_FORMAT, seqdata->seqmax));
@@ -484,15 +511,21 @@ deparse_Seq_Maxvalue_toJsonb(JsonbParseState *state,
* Deparse the sequence MINVALUE option to Jsonb
*
* Verbose syntax
+ * SET MINVALUE %{value}s
+ * OR
* MINVALUE %{value}s
*/
static inline void
deparse_Seq_Minvalue_toJsonb(JsonbParseState *state,
- Form_pg_sequence seqdata)
+ Form_pg_sequence seqdata, bool alter_table)
{
+ char *fmt;
+
+ fmt = alter_table ? "SET MINVALUE %{value}s" : "MINVALUE %{value}s";
+
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 3,
- "fmt", jbvString, "MINVALUE %{value}s",
+ "fmt", jbvString, fmt,
"clause", jbvString, "minvalue",
"value", jbvString,
psprintf(INT64_FORMAT, seqdata->seqmin));
@@ -506,7 +539,8 @@ deparse_Seq_Minvalue_toJsonb(JsonbParseState *state,
* OWNED BY %{owner}D
*/
static void
-deparse_Seq_OwnedBy_toJsonb(JsonbParseState *state, Oid sequenceId)
+deparse_Seq_OwnedBy_toJsonb(JsonbParseState *state, Oid sequenceId,
+ bool alter_table)
{
Relation depRel;
SysScanDesc scan;
@@ -591,15 +625,21 @@ deparse_Seq_OwnedBy_toJsonb(JsonbParseState *state, Oid sequenceId)
* Deparse the sequence START WITH option to Jsonb.
*
* Verbose syntax
+ * SET START WITH %{value}s
+ * OR
* START WITH %{value}s
*/
static inline void
deparse_Seq_Startwith_toJsonb(JsonbParseState *state,
- Form_pg_sequence seqdata)
+ Form_pg_sequence seqdata, bool alter_table)
{
+ char *fmt;
+
+ fmt = alter_table ? "SET START WITH %{value}s" : "START WITH %{value}s";
+
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 3,
- "fmt", jbvString, "START WITH %{value}s",
+ "fmt", jbvString, fmt,
"clause", jbvString, "start",
"value", jbvString,
psprintf(INT64_FORMAT, seqdata->seqstart));
@@ -643,14 +683,17 @@ deparse_Seq_As_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata)
* Deparse the definition of column identity to Jsonb.
*
* 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 void
deparse_ColumnIdentity_toJsonb(JsonbParseState *state, char *parentKey,
- Oid seqrelid, char identity)
+ Oid seqrelid, char identity, bool alter_table)
{
Form_pg_sequence seqform;
Sequence_values *seqvalues;
+ char *identfmt;
StringInfoData fmtStr;
initStringInfo(&fmtStr);
@@ -672,10 +715,11 @@ deparse_ColumnIdentity_toJsonb(JsonbParseState *state, char *parentKey,
insert_jsonb_key(state, "identity_type");
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ identfmt = alter_table ? "SET GENERATED %{option}s" :
+ "GENERATED %{option}s AS IDENTITY";
new_jsonb_VA(state, 2,
- "fmt", jbvString,
- "GENERATED %{option}s AS IDENTITY",
+ "fmt", jbvString, identfmt,
"option", jbvString,
(identity == ATTRIBUTE_IDENTITY_ALWAYS ?
"ALWAYS" : "BY DEFAULT"));
@@ -685,7 +729,8 @@ deparse_ColumnIdentity_toJsonb(JsonbParseState *state, char *parentKey,
/* seq_definition array object creation */
insert_jsonb_key(state, "seq_definition");
- appendStringInfoString(&fmtStr, " ( %{seq_definition: }s )");
+ appendStringInfoString(&fmtStr, alter_table ? " %{seq_definition: }s" :
+ " ( %{seq_definition: }s )");
pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
@@ -693,12 +738,12 @@ deparse_ColumnIdentity_toJsonb(JsonbParseState *state, char *parentKey,
seqform = seqvalues->seqform;
/* Definition elements */
- deparse_Seq_Cache_toJsonb(state, seqform);
- deparse_Seq_Cycle_toJsonb(state, seqform);
- deparse_Seq_IncrementBy_toJsonb(state, seqform);
- deparse_Seq_Minvalue_toJsonb(state, seqform);
- deparse_Seq_Maxvalue_toJsonb(state, seqform);
- deparse_Seq_Startwith_toJsonb(state, seqform);
+ deparse_Seq_Cache_toJsonb(state, seqform, alter_table);
+ deparse_Seq_Cycle_toJsonb(state, seqform, alter_table);
+ deparse_Seq_IncrementBy_toJsonb(state, seqform, alter_table);
+ deparse_Seq_Minvalue_toJsonb(state, seqform, alter_table);
+ deparse_Seq_Maxvalue_toJsonb(state, seqform, alter_table);
+ deparse_Seq_Startwith_toJsonb(state, seqform, alter_table);
deparse_Seq_Restart_toJsonb(state, seqvalues->last_value);
/* We purposefully do not emit OWNED BY here */
@@ -726,7 +771,8 @@ deparse_ColumnIdentity_toJsonb(JsonbParseState *state, char *parentKey,
*/
static void
deparse_ColumnDef_toJsonb(JsonbParseState *state, Relation relation,
- List *dpcontext, bool composite, ColumnDef *coldef)
+ List *dpcontext, bool composite, ColumnDef *coldef,
+ bool is_alter)
{
Oid relid = RelationGetRelid(relation);
HeapTuple attrTup;
@@ -831,6 +877,9 @@ deparse_ColumnDef_toJsonb(JsonbParseState *state, Relation relation,
}
}
+ if (is_alter && coldef->is_not_null)
+ saw_notnull = true;
+
/* NOT NULL */
if (saw_notnull)
{
@@ -877,7 +926,7 @@ deparse_ColumnDef_toJsonb(JsonbParseState *state, Relation relation,
appendStringInfoString(&fmtStr, " %{identity_column}s");
deparse_ColumnIdentity_toJsonb(state, "identity_column",
seqrelid,
- coldef->identity);
+ coldef->identity, is_alter);
}
}
@@ -1198,7 +1247,8 @@ deparse_TableElems_ToJsonb(JsonbParseState *state, Relation relation,
(ColumnDef *) elt);
else
deparse_ColumnDef_toJsonb(state, relation, dpcontext,
- composite, (ColumnDef *) elt);
+ composite, (ColumnDef *) elt,
+ false);
}
break;
case T_Constraint:
@@ -1464,6 +1514,93 @@ deparse_DefElem_ToJsonb(JsonbParseState *state, DefElem *elem, bool is_reset)
pushJsonbValue(&state, WJB_END_OBJECT, NULL);
}
+/*
+ * Deparse SET/RESET as used by
+ * ALTER TABLE ... ALTER COLUMN ... SET/RESET (...)
+ *
+ * Verbose syntax
+ * ALTER COLUMN %{column}I RESET|SET (%{options:, }s)
+ */
+static void
+deparse_ColumnSetOptions_ToJsonb(JsonbParseState *state, AlterTableCmd *subcmd)
+{
+ ListCell *cell;
+ bool is_reset = subcmd->subtype == AT_ResetOptions;
+ bool elem_found PG_USED_FOR_ASSERTS_ONLY = false;
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "ALTER COLUMN %{column}I"
+ " %{option}s (%{options:, }s)",
+ "column", jbvString, subcmd->name,
+ "option", jbvString, is_reset ? "RESET" : "SET");
+
+ insert_jsonb_key(state, "options");
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+ foreach(cell, (List *) subcmd->def)
+ {
+ DefElem *elem;
+
+ elem = (DefElem *) lfirst(cell);
+ deparse_DefElem_ToJsonb(state, elem, is_reset);
+
+#ifdef USE_ASSERT_CHECKING
+ elem_found = true;
+#endif
+ }
+
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+ Assert(elem_found);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse SET/RESET as used by ALTER TABLE ... SET/RESET (...)
+ *
+ * Verbose syntax
+ * RESET|SET (%{options:, }s)
+ */
+static void
+deparse_RelSetOptions_toJsonb(JsonbParseState *state, AlterTableCmd *subcmd)
+{
+ ListCell *cell;
+ bool is_reset = subcmd->subtype == AT_ResetRelOptions;
+ bool elem_found PG_USED_FOR_ASSERTS_ONLY = false;
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "%{set_reset}s (%{options:, }s)",
+ "set_reset", jbvString, is_reset ? "RESET" : "SET");
+
+ /* insert options array */
+ insert_jsonb_key(state, "options");
+
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+ foreach(cell, (List *) subcmd->def)
+ {
+ DefElem *elem;
+
+ elem = (DefElem *) lfirst(cell);
+ deparse_DefElem_ToJsonb(state, elem, is_reset);
+
+#ifdef USE_ASSERT_CHECKING
+ elem_found = true;
+#endif
+ }
+
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+ Assert(elem_found);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
/*
* Deparse WITH clause, as used by Create Table.
*/
@@ -1784,6 +1921,1297 @@ deparse_drop_table(const char *objidentity, const char *objecttype,
return JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
}
+/*
+ * Deparse all the collected subcommands and return jsonb string representing
+ * the alter command.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{only}s %{identity}D %{subcmds:, }s
+ */
+static Jsonb *
+deparse_AlterTableStmt(CollectedCommand *cmd)
+{
+ List *dpcontext;
+ Relation rel;
+ ListCell *cell;
+ const char *reltype;
+ Oid relId = cmd->d.alterTable.objectId;
+ AlterTableStmt *stmt = NULL;
+ StringInfoData fmtStr;
+ JsonbParseState *state = NULL;
+ bool subCmdArray = false;
+ JsonbValue *value;
+
+ Assert(cmd->type == SCT_AlterTable);
+ stmt = (AlterTableStmt *) cmd->parsetree;
+
+ Assert(IsA(stmt, AlterTableStmt) || IsA(stmt, AlterTableMoveAllStmt));
+
+ initStringInfo(&fmtStr);
+
+ /*
+ * ALTER TABLE subcommands generated for TableLikeClause is processed in
+ * the top level CREATE TABLE command; return empty here.
+ */
+ if (IsA(stmt, AlterTableStmt) && stmt->table_like)
+ return NULL;
+
+ rel = relation_open(relId, AccessShareLock);
+
+ switch (rel->rd_rel->relkind)
+ {
+ case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
+ reltype = "TABLE";
+ break;
+ case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
+ case RELKIND_VIEW:
+ case RELKIND_COMPOSITE_TYPE:
+ case RELKIND_FOREIGN_TABLE:
+ case RELKIND_MATVIEW:
+ /* unsupported relkind */
+ table_close(rel, AccessShareLock);
+ return NULL;
+
+ default:
+ elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
+ }
+
+
+ dpcontext = deparse_context_for(RelationGetRelationName(rel), relId);
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ /* Start constructing fmt string */
+ appendStringInfoString(&fmtStr, "ALTER %{objtype}s");
+
+ new_jsonb_VA(state, 1, "objtype", jbvString, reltype);
+
+ if (!stmt->relation->inh)
+ {
+ appendStringInfoString(&fmtStr, " %{only}s");
+ new_jsonb_VA(state, 1, "only", jbvString, "ONLY");
+ }
+
+ appendStringInfoString(&fmtStr, " %{identity}D");
+ insert_identity_object(state, rel->rd_rel->relnamespace,
+ RelationGetRelationName(rel));
+
+ foreach(cell, cmd->d.alterTable.subcmds)
+ {
+ CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
+ AlterTableCmd *subcmd = (AlterTableCmd *) sub->parsetree;
+
+ Assert(IsA(subcmd, AlterTableCmd));
+
+ /*
+ * Skip deparse of the subcommand if the objectId doesn't match the
+ * target relation ID. It can happen for inherited tables when
+ * subcommands for inherited tables and the parent table are both
+ * collected in the ALTER TABLE command for the parent table.
+ */
+ if (subcmd->subtype != AT_AttachPartition &&
+ sub->address.objectId != relId &&
+ has_superclass(sub->address.objectId))
+ continue;
+
+ /* Mark the begin of subcmds array */
+ if (!subCmdArray)
+ {
+ appendStringInfoString(&fmtStr, " %{subcmds:, }s");
+ insert_jsonb_key(state, "subcmds");
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+ subCmdArray = true;
+ }
+
+ switch (subcmd->subtype)
+ {
+ case AT_AddColumn:
+ {
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ /* XXX need to set the "recurse" bit somewhere? */
+ Assert(IsA(subcmd->def, ColumnDef));
+
+ /*
+ * Syntax: ADD COLUMN %{if_not_exists}s %{definition}s"
+ * where definition: "%{name}I %{coltype}T STORAGE
+ * %{colstorage}s %{compression}s %{collation}s
+ * %{not_null}s %{default}s %{identity_column}s
+ * %{generated_column}s"
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ appendStringInfoString(&fmtSub, "ADD COLUMN");
+ new_jsonb_VA(state, 1, "type", jbvString, "add column");
+
+ if (subcmd->missing_ok)
+ {
+ appendStringInfoString(&fmtSub, " %{if_not_exists}s");
+ new_jsonb_VA(state, 1,
+ "if_not_exists", jbvString, "IF NOT EXISTS");
+ }
+
+ /* Push definition key-value pair */
+ appendStringInfoString(&fmtSub, " %{definition}s");
+ insert_jsonb_key(state, "definition");
+
+ deparse_ColumnDef_toJsonb(state, rel, dpcontext,
+ false, (ColumnDef *) subcmd->def,
+ true);
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+ pfree(fmtSub.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+ }
+
+ case AT_AddIndexConstraint:
+ {
+ IndexStmt *istmt;
+ Relation idx;
+ Oid conOid = sub->address.objectId;
+
+ Assert(IsA(subcmd->def, IndexStmt));
+ istmt = (IndexStmt *) subcmd->def;
+
+ Assert(istmt->isconstraint && istmt->unique);
+
+ idx = relation_open(istmt->indexOid, AccessShareLock);
+
+ /*
+ * Syntax: ADD CONSTRAINT %{name}I %{constraint_type}s
+ * USING INDEX %index_name}I %{deferrable}s
+ * %{init_deferred}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 7,
+ "fmt", jbvString,
+ "ADD CONSTRAINT %{name}I %{constraint_type}s"
+ " USING INDEX %{index_name}I %{deferrable}s"
+ " %{init_deferred}s",
+ "type", jbvString, "add constraint using index",
+ "name", jbvString, get_constraint_name(conOid),
+ "constraint_type", jbvString,
+ istmt->primary ? "PRIMARY KEY" : "UNIQUE",
+ "index_name", jbvString,
+ RelationGetRelationName(idx),
+ "deferrable", jbvString,
+ istmt->deferrable ? "DEFERRABLE" :
+ "NOT DEFERRABLE",
+ "init_deferred", jbvString,
+ istmt->initdeferred ? "INITIALLY DEFERRED" :
+ "INITIALLY IMMEDIATE");
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ relation_close(idx, AccessShareLock);
+ break;
+ }
+ case AT_ReAddIndex:
+ case AT_ReAddConstraint:
+ case AT_ReAddDomainConstraint:
+ case AT_ReAddComment:
+ case AT_ReplaceRelOptions:
+ case AT_CheckNotNull:
+ case AT_ReAddStatistics:
+ /* Subtypes used for internal operations; nothing to do here */
+ break;
+
+ case AT_ColumnDefault:
+ if (subcmd->def == NULL)
+ {
+ /*
+ * Syntax: ALTER COLUMN %{column}I DROP DEFAULT
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString,
+ "ALTER COLUMN %{column}I DROP DEFAULT",
+ "type", jbvString, "drop default",
+ "column", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+ else
+ {
+ List *dpcontext_rel;
+ HeapTuple attrtup;
+ AttrNumber attno;
+
+ dpcontext_rel = deparse_context_for(
+ RelationGetRelationName(rel),
+ RelationGetRelid(rel));
+ attrtup = SearchSysCacheAttName(RelationGetRelid(rel),
+ subcmd->name);
+ attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I SET DEFAULT
+ * %{definition}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 4,
+ "fmt", jbvString,
+ "ALTER COLUMN %{column}I SET DEFAULT"
+ " %{definition}s",
+ "type", jbvString, "set default",
+ "column", jbvString, subcmd->name,
+ "definition", jbvString,
+ RelationGetColumnDefault(rel, attno,
+ dpcontext_rel));
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ ReleaseSysCache(attrtup);
+ }
+
+ break;
+
+ case AT_DropNotNull:
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I DROP NOT NULL
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString,
+ "ALTER COLUMN %{column}I DROP NOT NULL",
+ "type", jbvString, "drop not null",
+ "column", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_ForceRowSecurity:
+
+ /*
+ * Syntax: FORCE ROW LEVEL SECURITY
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 1, "fmt", jbvString,
+ "FORCE ROW LEVEL SECURITY");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_NoForceRowSecurity:
+
+ /*
+ * Syntax: NO FORCE ROW LEVEL SECURITY
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 1, "fmt", jbvString,
+ "NO FORCE ROW LEVEL SECURITY");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_SetNotNull:
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I SET NOT NULL
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString,
+ "ALTER COLUMN %{column}I SET NOT NULL",
+ "type", jbvString, "set not null",
+ "column", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DropExpression:
+ {
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I DROP EXPRESSION
+ * %{if_exists}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ appendStringInfoString(&fmtSub, "ALTER COLUMN"
+ " %{column}I DROP EXPRESSION");
+ new_jsonb_VA(state, 2,
+ "type", jbvString, "drop expression",
+ "column", jbvString, subcmd->name);
+
+ if (subcmd->missing_ok)
+ {
+ appendStringInfoString(&fmtSub, " %{if_exists}s");
+ new_jsonb_VA(state, 1,
+ "if_exists", jbvString, "IF EXISTS");
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+ pfree(fmtSub.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+ }
+
+ case AT_SetStatistics:
+ Assert(IsA(subcmd->def, Integer));
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I SET STATISTICS
+ * %{statistics}n
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 4,
+ "fmt", jbvString,
+ "ALTER COLUMN %{column}I SET STATISTICS"
+ " %{statistics}n",
+ "type", jbvString, "set statistics",
+ "column", jbvString, subcmd->name,
+ "statistics", jbvNumeric,
+ intVal((Integer *) subcmd->def));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_SetOptions:
+ case AT_ResetOptions:
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I RESET|SET (%{options:, }s)
+ */
+ deparse_ColumnSetOptions_ToJsonb(state, subcmd);
+ break;
+
+ case AT_SetStorage:
+ Assert(IsA(subcmd->def, String));
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I SET STORAGE %{storage}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 4,
+ "fmt", jbvString,
+ "ALTER COLUMN %{column}I SET STORAGE"
+ " %{storage}s",
+ "type", jbvString, "set storage",
+ "column", jbvString, subcmd->name,
+ "storage", jbvString,
+ strVal((String *) subcmd->def));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_SetCompression:
+ Assert(IsA(subcmd->def, String));
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I SET COMPRESSION
+ * %{compression_method}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 4,
+ "fmt", jbvString,
+ "ALTER COLUMN %{column}I SET COMPRESSION"
+ " %{compression_method}s",
+ "type", jbvString, "set compression",
+ "column", jbvString, subcmd->name,
+ "compression_method", jbvString,
+ strVal((String *) subcmd->def));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DropColumn:
+ {
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ /*
+ * Syntax: DROP COLUMN %{if_exists}s %{column}I
+ * %{cascade}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ appendStringInfoString(&fmtSub, "DROP COLUMN");
+ new_jsonb_VA(state, 1, "type", jbvString, "drop column");
+
+ if (subcmd->missing_ok)
+ {
+ appendStringInfoString(&fmtSub, " %{if_exists}s");
+ new_jsonb_VA(state, 1,
+ "if_exists", jbvString, "IF EXISTS");
+ }
+
+ appendStringInfoString(&fmtSub, " %{column}I");
+ new_jsonb_VA(state, 1, "column", jbvString, subcmd->name);
+
+ if (subcmd->behavior == DROP_CASCADE)
+ {
+ appendStringInfoString(&fmtSub, " %{cascade}s");
+ new_jsonb_VA(state, 1,
+ "cascade", jbvString, "CASCADE");
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+ pfree(fmtSub.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ break;
+ }
+ case AT_AddIndex:
+ {
+ Oid idxOid = sub->address.objectId;
+ IndexStmt *istmt PG_USED_FOR_ASSERTS_ONLY =
+ (IndexStmt *) subcmd->def;
+ Relation idx;
+ const char *idxname;
+ Oid constrOid;
+
+ Assert(IsA(subcmd->def, IndexStmt));
+ Assert(istmt->isconstraint);
+
+ idx = relation_open(idxOid, AccessShareLock);
+ idxname = RelationGetRelationName(idx);
+
+ constrOid = get_relation_constraint_oid(
+ cmd->d.alterTable.objectId,
+ idxname, false);
+
+ /*
+ * Syntax: ADD CONSTRAINT %{name}I %{definition}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 4,
+ "fmt", jbvString,
+ "ADD CONSTRAINT %{name}I %{definition}s",
+ "type", jbvString, "add constraint",
+ "name", jbvString, idxname,
+ "definition", jbvString,
+ pg_get_constraintdef_string(constrOid));
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ relation_close(idx, AccessShareLock);
+ }
+ break;
+ case AT_AddConstraint:
+ {
+ /* XXX need to set the "recurse" bit somewhere? */
+ Oid constrOid = sub->address.objectId;
+
+ /* Skip adding constraint for inherits table sub command */
+ if (!OidIsValid(constrOid))
+ continue;
+
+ Assert(IsA(subcmd->def, Constraint));
+
+ /*
+ * Syntax: ADD CONSTRAINT %{name}I %{definition}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 4,
+ "fmt", jbvString,
+ "ADD CONSTRAINT %{name}I %{definition}s",
+ "type", jbvString, "add constraint",
+ "name", jbvString, get_constraint_name(constrOid),
+ "definition", jbvString,
+ pg_get_constraintdef_string(constrOid));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ break;
+ }
+
+ case AT_AlterConstraint:
+ {
+ Oid conOid = sub->address.objectId;
+ Constraint *c = (Constraint *) subcmd->def;
+
+ /* If no constraint was altered, silently skip it */
+ if (!OidIsValid(conOid))
+ break;
+
+ Assert(IsA(c, Constraint));
+
+ /*
+ * Syntax: ALTER CONSTRAINT %{name}I %{deferrable}s
+ * %{init_deferred}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 5,
+ "fmt", jbvString,
+ "ALTER CONSTRAINT %{name}I %{deferrable}s"
+ " %{init_deferred}s",
+ "type", jbvString, "alter constraint",
+ "name", jbvString, get_constraint_name(conOid),
+ "deferrable", jbvString,
+ c->deferrable ? "DEFERRABLE" : "NOT DEFERRABLE",
+ "init_deferred", jbvString,
+ c->initdeferred ? "INITIALLY DEFERRED" :
+ "INITIALLY IMMEDIATE");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+ break;
+
+ case AT_ValidateConstraint:
+
+ /*
+ * Syntax: VALIDATE CONSTRAINT %{constraint}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString,
+ "VALIDATE CONSTRAINT %{constraint}I",
+ "type", jbvString, "validate constraint",
+ "constraint", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DropConstraint:
+ {
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ /*
+ * Syntax: DROP CONSTRAINT %{if_exists}s %{constraint}I
+ * %{cascade}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ appendStringInfoString(&fmtSub, "DROP CONSTRAINT");
+ new_jsonb_VA(state, 1, "type", jbvString, "drop constraint");
+
+ if (subcmd->missing_ok)
+ {
+ appendStringInfoString(&fmtSub, " %{if_exists}s");
+ new_jsonb_VA(state, 1,
+ "if_exists", jbvString, "IF EXISTS");
+ }
+
+ appendStringInfoString(&fmtSub, " %{constraint}I");
+ new_jsonb_VA(state, 1,
+ "constraint", jbvString, subcmd->name);
+
+ if (subcmd->behavior == DROP_CASCADE)
+ {
+ appendStringInfoString(&fmtSub, " %{cascade}s");
+ new_jsonb_VA(state, 1, "cascade", jbvString, "CASCADE");
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+ pfree(fmtSub.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+ break;
+
+ case AT_AlterColumnType:
+ {
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ Form_pg_attribute att;
+ ColumnDef *def;
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ att = &(tupdesc->attrs[sub->address.objectSubId - 1]);
+ def = (ColumnDef *) subcmd->def;
+ Assert(IsA(def, ColumnDef));
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I SET DATA TYPE
+ * %{datatype}T %{collation}s %{using}s where using: USING
+ * %{expression}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ appendStringInfoString(&fmtSub, "ALTER COLUMN %{column}I"
+ " SET DATA TYPE %{datatype}T");
+ new_jsonb_VA(state, 2,
+ "type", jbvString, "alter column type",
+ "column", jbvString, subcmd->name);
+
+ new_jsonb_for_type(state, "datatype",
+ att->atttypid, att->atttypmod);
+
+ /* Add a COLLATE clause, if needed */
+ if (OidIsValid(att->attcollation))
+ {
+ appendStringInfoString(&fmtSub, " %{collation}s");
+ insert_collate_object(state, "collation",
+ "COLLATE %{name}D",
+ CollationRelationId,
+ att->attcollation, "name");
+ }
+
+ /*
+ * If there's a USING clause, transformAlterTableStmt ran
+ * it through transformExpr and stored the resulting node
+ * in cooked_default, which we can use here.
+ */
+ if (def->raw_default)
+ {
+ Datum deparsed;
+ char *defexpr;
+ List *exprs = NIL;
+
+ exprs = lappend(exprs, def->cooked_default);
+ defexpr = nodeToString(def->cooked_default);
+ deparsed = DirectFunctionCall2(pg_get_expr,
+ CStringGetTextDatum(defexpr),
+ RelationGetRelid(rel));
+ appendStringInfoString(&fmtSub, " %{using}s");
+ insert_jsonb_key(state, "using");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "USING %{expression}s",
+ "expression", jbvString,
+ TextDatumGetCString(deparsed));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+
+ pfree(fmtSub.data);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+ break;
+
+ case AT_ChangeOwner:
+
+ /*
+ * Syntax: OWNER TO %{owner}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "OWNER TO %{owner}I",
+ "type", jbvString, "change owner",
+ "owner", jbvString,
+ get_rolespec_name(subcmd->newowner));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_ClusterOn:
+
+ /*
+ * Syntax: CLUSTER ON %{index}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "CLUSTER ON %{index}I",
+ "type", jbvString, "cluster on",
+ "index", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+
+ case AT_DropCluster:
+
+ /*
+ * Syntax: SET WITHOUT CLUSTER
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "SET WITHOUT CLUSTER",
+ "type", jbvString, "set without cluster");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_SetLogged:
+
+ /*
+ * Syntax: SET LOGGED
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "SET LOGGED",
+ "type", jbvString, "set logged");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_SetUnLogged:
+
+ /*
+ * Syntax: SET UNLOGGED
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "SET UNLOGGED",
+ "type", jbvString, "set unlogged");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DropOids:
+
+ /*
+ * Syntax: SET WITHOUT OIDS
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "SET WITHOUT OIDS",
+ "type", jbvString, "set without oids");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_SetAccessMethod:
+
+ /*
+ * Syntax: SET ACCESS METHOD %{access_method}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString,
+ "SET ACCESS METHOD %{access_method}I",
+ "type", jbvString, "set access method",
+ "access_method", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_SetTableSpace:
+
+ /*
+ * Syntax: SET TABLESPACE %{tablespace}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "SET TABLESPACE %{tablespace}I",
+ "type", jbvString, "set tablespace",
+ "tablespace", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_SetRelOptions:
+ case AT_ResetRelOptions:
+
+ /*
+ * Syntax: SET|RESET (%{options:, }s)
+ */
+ deparse_RelSetOptions_toJsonb(state, subcmd);
+ break;
+
+ case AT_EnableTrig:
+
+ /*
+ * Syntax: ENABLE TRIGGER %{trigger}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "ENABLE TRIGGER %{trigger}I",
+ "type", jbvString, "enable trigger",
+ "trigger", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_EnableAlwaysTrig:
+
+ /*
+ * Syntax: ENABLE ALWAYS TRIGGER %{trigger}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString,
+ "ENABLE ALWAYS TRIGGER %{trigger}I",
+ "type", jbvString, "enable always trigger",
+ "trigger", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_EnableReplicaTrig:
+
+ /*
+ * Syntax: ENABLE REPLICA TRIGGER %{trigger}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString,
+ "ENABLE REPLICA TRIGGER %{trigger}I",
+ "type", jbvString, "enable replica trigger",
+ "trigger", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DisableTrig:
+
+ /*
+ * Syntax: DISABLE TRIGGER %{trigger}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "DISABLE TRIGGER %{trigger}I",
+ "type", jbvString, "disable trigger",
+ "trigger", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_EnableTrigAll:
+
+ /*
+ * Syntax: ENABLE TRIGGER ALL
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "ENABLE TRIGGER ALL",
+ "type", jbvString, "enable trigger all");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DisableTrigAll:
+
+ /*
+ * Syntax: DISABLE TRIGGER ALL
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "DISABLE TRIGGER ALL",
+ "type", jbvString, "disable trigger all");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_EnableTrigUser:
+
+ /*
+ * Syntax: ENABLE TRIGGER USER
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "ENABLE TRIGGER USER",
+ "type", jbvString, "enable trigger user");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DisableTrigUser:
+
+ /*
+ * Syntax: DISABLE TRIGGER USER
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "DISABLE TRIGGER USER",
+ "type", jbvString, "disable trigger user");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_EnableRule:
+
+ /*
+ * Syntax: ENABLE RULE %{rule}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "ENABLE RULE %{rule}I",
+ "type", jbvString, "enable rule",
+ "rule", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_EnableAlwaysRule:
+
+ /*
+ * Syntax: ENABLE ALWAYS RULE %{rule}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "ENABLE ALWAYS RULE %{rule}I",
+ "type", jbvString, "enable always rule",
+ "rule", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_EnableReplicaRule:
+
+ /*
+ * Syntax: ENABLE REPLICA RULE %{rule}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "ENABLE REPLICA RULE %{rule}I",
+ "type", jbvString, "enable replica rule",
+ "rule", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DisableRule:
+
+ /*
+ * Syntax: DISABLE RULE %{rule}I
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "DISABLE RULE %{rule}I",
+ "type", jbvString, "disable rule",
+ "rule", jbvString, subcmd->name);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_AddInherit:
+
+ /*
+ * Syntax: INHERIT %{parent}D
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "INHERIT %{parent}D",
+ "type", jbvString, "inherit");
+ new_jsonb_for_qualname_id(state, RelationRelationId,
+ sub->address.objectId, "parent", true);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DropInherit:
+
+ /*
+ * Syntax: NO INHERIT %{parent}D
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "NO INHERIT %{parent}D",
+ "type", jbvString, "drop inherit");
+ new_jsonb_for_qualname_id(state, RelationRelationId,
+ sub->address.objectId, "parent", true);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_AddOf:
+
+ /*
+ * Syntax: OF %{type_of}T
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "OF %{type_of}T",
+ "type", jbvString, "add of");
+ new_jsonb_for_type(state, "type_of", sub->address.objectId, -1);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DropOf:
+
+ /*
+ * Syntax: NOT OF
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "NOT OF",
+ "type", jbvString, "not of");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_ReplicaIdentity:
+
+ /*
+ * Syntax: REPLICA IDENTITY %{ident}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString,
+ "REPLICA IDENTITY %{ident}s",
+ "type", jbvString, "replica identity");
+ switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type)
+ {
+ case REPLICA_IDENTITY_DEFAULT:
+ new_jsonb_VA(state, 1, "ident", jbvString, "DEFAULT");
+ break;
+ case REPLICA_IDENTITY_FULL:
+ new_jsonb_VA(state, 1, "ident", jbvString, "FULL");
+ break;
+ case REPLICA_IDENTITY_NOTHING:
+ new_jsonb_VA(state, 1, "ident", jbvString, "NOTHING");
+ break;
+ case REPLICA_IDENTITY_INDEX:
+ insert_jsonb_key(state, "ident");
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "USING INDEX %{index}I",
+ "index", jbvString,
+ ((ReplicaIdentityStmt *) subcmd->def)->name);
+ break;
+ }
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_EnableRowSecurity:
+
+ /*
+ * Syntax: ENABLE ROW LEVEL SECURITY
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString,
+ "ENABLE ROW LEVEL SECURITY",
+ "type", jbvString, "enable row security");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_DisableRowSecurity:
+
+ /*
+ * Syntax: DISABLE ROW LEVEL SECURITY
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString,
+ "DISABLE ROW LEVEL SECURITY",
+ "type", jbvString, "disable row security");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+
+ case AT_AttachPartition:
+ {
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ /*
+ * Syntax: ATTACH PARTITION %{partition_identity}D
+ * %{partition_bound}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ appendStringInfoString(&fmtSub, "ATTACH PARTITION"
+ " %{partition_identity}D");
+
+ new_jsonb_VA(state, 1, "type", jbvString,
+ "attach partition");
+ new_jsonb_for_qualname_id(state, RelationRelationId,
+ sub->address.objectId,
+ "partition_identity", true);
+
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ appendStringInfoString(&fmtSub, " %{partition_bound}s");
+ new_jsonb_VA(state, 1,
+ "partition_bound", jbvString,
+ RelationGetPartitionBound(sub->address.objectId));
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+ pfree(fmtSub.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ break;
+ }
+ case AT_DetachPartition:
+ {
+ PartitionCmd *cmd;
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ Assert(IsA(subcmd->def, PartitionCmd));
+ cmd = (PartitionCmd *) subcmd->def;
+
+ /*
+ * Syntax: DETACH PARTITION %{partition_identity}D
+ * %{concurrent}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ appendStringInfoString(&fmtSub, "DETACH PARTITION"
+ " %{partition_identity}D");
+
+ new_jsonb_VA(state, 1, "type", jbvString, "detach partition");
+ new_jsonb_for_qualname_id(state, RelationRelationId,
+ sub->address.objectId,
+ "partition_identity", true);
+ if (cmd->concurrent)
+ {
+ appendStringInfoString(&fmtSub, " %{concurrent}s");
+ new_jsonb_VA(state, 1,
+ "concurrent", jbvString, "CONCURRENTLY");
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+ pfree(fmtSub.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+ }
+ case AT_DetachPartitionFinalize:
+
+ /*
+ * Syntax: DETACH PARTITION %{partition_identity}D FINALIZE
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "DETACH PARTITION"
+ " %{partition_identity}D FINALIZE",
+ "type", jbvString, "detach partition finalize");
+
+ new_jsonb_for_qualname_id(state, RelationRelationId,
+ sub->address.objectId,
+ "partition_identity", true);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+ case AT_AddIdentity:
+ {
+ AttrNumber attnum;
+ Oid seq_relid;
+ ColumnDef *coldef = (ColumnDef *) subcmd->def;
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I %{definition}s where
+ * definition : ADD %{identity_column}s where
+ * identity_column: %{identity_type}s ( %{seq_definition:
+ * }s )
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ appendStringInfoString(&fmtSub, "ALTER COLUMN %{column}I");
+ new_jsonb_VA(state, 2,
+ "type", jbvString, "add identity",
+ "column", jbvString, subcmd->name);
+
+ attnum = get_attnum(RelationGetRelid(rel), subcmd->name);
+ seq_relid = getIdentitySequence(RelationGetRelid(rel),
+ attnum, true);
+
+ if (OidIsValid(seq_relid))
+ {
+
+ appendStringInfoString(&fmtSub, " %{definition}s");
+ insert_jsonb_key(state, "definition");
+
+ /* insert definition's value now */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 1,
+ "fmt", jbvString, "ADD %{identity_column}s");
+
+ /* insert identity_column */
+ deparse_ColumnIdentity_toJsonb(state, "identity_column",
+ seq_relid,
+ coldef->identity, false);
+
+ /* mark definition's value end */
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+
+ pfree(fmtSub.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+ break;
+ case AT_SetIdentity:
+ {
+ DefElem *defel;
+ char identity = 0;
+ AttrNumber attnum;
+ Oid seq_relid;
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I %{definition}s where
+ * definition : %{identity_type}s ( %{seq_definition: }s )
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ appendStringInfoString(&fmtSub, "ALTER COLUMN %{column}I");
+ new_jsonb_VA(state, 2,
+ "type", jbvString, "set identity",
+ "column", jbvString, subcmd->name);
+
+ if (subcmd->def)
+ {
+ List *def = (List *) subcmd->def;
+
+ Assert(IsA(subcmd->def, List));
+
+ defel = linitial_node(DefElem, def);
+ identity = defGetInt32(defel);
+ }
+
+ attnum = get_attnum(RelationGetRelid(rel), subcmd->name);
+ seq_relid = getIdentitySequence(RelationGetRelid(rel),
+ attnum, true);
+
+ if (OidIsValid(seq_relid))
+ {
+ appendStringInfoString(&fmtSub, " %{definition}s");
+ deparse_ColumnIdentity_toJsonb(state, "definition",
+ seq_relid, identity,
+ true);
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+
+ pfree(fmtSub.data);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ break;
+ }
+ case AT_DropIdentity:
+ {
+ StringInfoData fmtSub;
+
+ initStringInfo(&fmtSub);
+
+ /*
+ * Syntax: ALTER COLUMN %{column}I DROP IDENTITY
+ * %{if_exists}s
+ */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ appendStringInfoString(&fmtSub, "ALTER COLUMN"
+ " %{column}I DROP IDENTITY");
+ new_jsonb_VA(state, 2,
+ "type", jbvString, "drop identity",
+ "column", jbvString, subcmd->name);
+
+ if (subcmd->missing_ok)
+ {
+ appendStringInfoString(&fmtSub, " %{if_exists}s");
+ new_jsonb_VA(state, 1,
+ "if_exists", jbvString, "IF EXISTS");
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
+ pfree(fmtSub.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ break;
+ }
+ default:
+ elog(WARNING, "unsupported alter table subtype %d",
+ subcmd->subtype);
+ break;
+ }
+ }
+
+ table_close(rel, AccessShareLock);
+
+ /* if subcmds array is not even created or has 0 elements, return NULL */
+ if (!subCmdArray ||
+ ((state->contVal.type == jbvArray) &&
+ (state->contVal.val.array.nElems == 0)))
+ {
+ pfree(fmtStr.data);
+ return NULL;
+ }
+
+ /* Mark the end of subcmds array */
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+ pfree(fmtStr.data);
+
+ /* Mark the end of ROOT object */
+ value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ return JsonbValueToJsonb(value);
+}
+
/*
* Deparse a CreateSeqStmt.
*
@@ -1862,12 +3290,12 @@ deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
/* Definition elements */
- deparse_Seq_Cache_toJsonb(state, seqform);
- deparse_Seq_Cycle_toJsonb(state, seqform);
- deparse_Seq_IncrementBy_toJsonb(state, seqform);
- deparse_Seq_Minvalue_toJsonb(state, seqform);
- deparse_Seq_Maxvalue_toJsonb(state, seqform);
- deparse_Seq_Startwith_toJsonb(state, seqform);
+ deparse_Seq_Cache_toJsonb(state, seqform, false);
+ deparse_Seq_Cycle_toJsonb(state, seqform, false);
+ deparse_Seq_IncrementBy_toJsonb(state, seqform, false);
+ deparse_Seq_Minvalue_toJsonb(state, seqform, false);
+ deparse_Seq_Maxvalue_toJsonb(state, seqform, false);
+ deparse_Seq_Startwith_toJsonb(state, seqform, false);
deparse_Seq_Restart_toJsonb(state, seqvalues->last_value);
deparse_Seq_As_toJsonb(state, seqform);
/* We purposefully do not emit OWNED BY here */
@@ -1946,21 +3374,21 @@ deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
DefElem *elem = (DefElem *) lfirst(cell);
if (strcmp(elem->defname, "cache") == 0)
- deparse_Seq_Cache_toJsonb(state, seqform);
+ deparse_Seq_Cache_toJsonb(state, seqform, false);
else if (strcmp(elem->defname, "cycle") == 0)
- deparse_Seq_Cycle_toJsonb(state, seqform);
+ deparse_Seq_Cycle_toJsonb(state, seqform, false);
else if (strcmp(elem->defname, "increment") == 0)
- deparse_Seq_IncrementBy_toJsonb(state, seqform);
+ deparse_Seq_IncrementBy_toJsonb(state, seqform, false);
else if (strcmp(elem->defname, "minvalue") == 0)
- deparse_Seq_Minvalue_toJsonb(state, seqform);
+ deparse_Seq_Minvalue_toJsonb(state, seqform, false);
else if (strcmp(elem->defname, "maxvalue") == 0)
- deparse_Seq_Maxvalue_toJsonb(state, seqform);
+ deparse_Seq_Maxvalue_toJsonb(state, seqform, false);
else if (strcmp(elem->defname, "start") == 0)
- deparse_Seq_Startwith_toJsonb(state, seqform);
+ deparse_Seq_Startwith_toJsonb(state, seqform, false);
else if (strcmp(elem->defname, "restart") == 0)
deparse_Seq_Restart_toJsonb(state, seqvalues->last_value);
else if (strcmp(elem->defname, "owned_by") == 0)
- deparse_Seq_OwnedBy_toJsonb(state, objectId);
+ deparse_Seq_OwnedBy_toJsonb(state, objectId, false);
else if (strcmp(elem->defname, "as") == 0)
deparse_Seq_As_toJsonb(state, seqform);
else
@@ -1976,6 +3404,209 @@ deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
return JsonbValueToJsonb(value);
}
+/*
+ * 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 Jsonb *
+deparse_RenameStmt(ObjectAddress address, Node *parsetree)
+{
+ RenameStmt *node = (RenameStmt *) parsetree;
+ Relation relation;
+ Oid schemaId;
+ JsonbParseState *state = NULL;
+ JsonbValue *value;
+
+ /*
+ * In an ALTER .. RENAME command, we don't have the original name of the
+ * object in system catalogs: since we inspect them after the command has
+ * executed, the old name is already gone. Therefore, we extract it from
+ * the parse node. Note we still extract the schema name from the catalog
+ * (it might not be present in the parse node); it cannot possibly have
+ * changed anyway.
+ */
+ switch (node->renameType)
+ {
+ case OBJECT_TABLE:
+ relation = relation_open(address.objectId, AccessShareLock);
+ schemaId = RelationGetNamespace(relation);
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 4,
+ "fmt", jbvString,
+ "ALTER %{objtype}s %{if_exists}s %{identity}D"
+ " RENAME TO %{newname}I",
+ "objtype", jbvString,
+ stringify_objtype(node->renameType),
+ "if_exists", jbvString,
+ node->missing_ok ? "IF EXISTS" : "",
+ "newname", jbvString, node->newname);
+
+ insert_identity_object(state, schemaId, node->relation->relname);
+ value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ relation_close(relation, AccessShareLock);
+ break;
+
+ case OBJECT_TABCONSTRAINT:
+ {
+ HeapTuple constrtup;
+ Form_pg_constraint constform;
+
+ constrtup = SearchSysCache1(CONSTROID,
+ ObjectIdGetDatum(address.objectId));
+ if (!HeapTupleIsValid(constrtup))
+ elog(ERROR, "cache lookup failed for constraint with OID %u",
+ address.objectId);
+ constform = (Form_pg_constraint) GETSTRUCT(constrtup);
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 4,
+ "fmt", jbvString,
+ "ALTER TABLE %{only}s %{identity}D RENAME"
+ " CONSTRAINT %{oldname}I TO %{newname}I",
+ "only", jbvString,
+ node->relation->inh ? "" : "ONLY",
+ "oldname", jbvString, node->subname,
+ "newname", jbvString, node->newname);
+
+ new_jsonb_for_qualname_id(state, RelationRelationId,
+ constform->conrelid, "identity", true);
+ value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ ReleaseSysCache(constrtup);
+ }
+ break;
+
+ case OBJECT_COLUMN:
+ {
+ StringInfoData fmtStr;
+
+ initStringInfo(&fmtStr);
+
+ relation = relation_open(address.objectId, AccessShareLock);
+ schemaId = RelationGetNamespace(relation);
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ appendStringInfoString(&fmtStr, "ALTER %{objtype}s");
+
+ new_jsonb_VA(state, 1,
+ "objtype", jbvString,
+ stringify_objtype(node->relationType));
+
+ /* Composite types do not support IF EXISTS */
+ if (node->renameType == OBJECT_COLUMN)
+ {
+ appendStringInfoString(&fmtStr, " %{if_exists}s");
+ new_jsonb_VA(state, 1,
+ "if_exists", jbvString,
+ node->missing_ok ? "IF EXISTS" : "");
+ }
+
+ if (!node->relation->inh)
+ {
+ appendStringInfoString(&fmtStr, " %{only}s");
+ new_jsonb_VA(state, 1, "only", jbvString, "ONLY");
+ }
+
+ appendStringInfoString(&fmtStr, " %{identity}D RENAME COLUMN"
+ " %{colname}I TO %{newname}I");
+ insert_identity_object(state, schemaId, node->relation->relname);
+ new_jsonb_VA(state, 2,
+ "colname", jbvString, node->subname,
+ "newname", jbvString, node->newname);
+
+ if (node->renameType == OBJECT_ATTRIBUTE)
+ {
+
+ if (node->behavior == DROP_CASCADE)
+ {
+ appendStringInfoString(&fmtStr, " %{cascade}s");
+ new_jsonb_VA(state, 1, "cascade", jbvString, "CASCADE");
+ }
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+ pfree(fmtStr.data);
+
+ value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ relation_close(relation, AccessShareLock);
+ break;
+ }
+
+ default:
+ elog(ERROR, "unsupported object type %d", node->renameType);
+ }
+
+ return JsonbValueToJsonb(value);
+}
+
+/*
+ * Deparse an AlterObjectSchemaStmt (ALTER TABLE... SET SCHEMA command)
+ *
+ * Given the object(table) address and the parse tree that created it, return
+ * Jsonb representing the alter command.
+ *
+ * Verbose syntax
+ * ALTER %{objtype}s %{identity}s SET SCHEMA %{newschema}I
+ */
+static Jsonb *
+deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
+ ObjectAddress old_schema)
+{
+ AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
+ char *identity;
+ char *new_schema = node->newschema;
+ char *old_schname;
+ char *ident;
+ JsonbParseState *state = NULL;
+ JsonbValue *value;
+
+ /*
+ * Since the command has already taken place from the point of view of
+ * catalogs, getObjectIdentity returns the object name with the already
+ * changed schema. The output of our deparsing must return the original
+ * schema name, however, so we chop the schema name off the identity
+ * string and then prepend the quoted schema name.
+ *
+ * XXX This is pretty clunky. Can we do better?
+ */
+ identity = getObjectIdentity(&address, false);
+ old_schname = get_namespace_name(old_schema.objectId);
+ if (!old_schname)
+ elog(ERROR, "cache lookup failed for schema with OID %u",
+ old_schema.objectId);
+
+ ident = psprintf("%s%s", quote_identifier(old_schname),
+ identity + strlen(quote_identifier(new_schema)));
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 4,
+ "fmt", jbvString,
+ "ALTER %{objtype}s %{identity}s SET SCHEMA"
+ " %{newschema}I",
+ "objtype", jbvString,
+ stringify_objtype(node->objectType),
+ "identity", jbvString, ident,
+ "newschema", jbvString, new_schema);
+ value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ return JsonbValueToJsonb(value);
+}
+
/*
* Handle deparsing of simple commands.
*
@@ -1998,6 +3629,11 @@ deparse_simple_command(CollectedCommand *cmd)
/* This switch needs to handle everything that ProcessUtilitySlow does */
switch (nodeTag(parsetree))
{
+ case T_AlterObjectSchemaStmt:
+ return deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
+ parsetree,
+ cmd->d.simple.secondaryObject);
+
case T_AlterSeqStmt:
return deparse_AlterSeqStmt(objectId, parsetree);
@@ -2006,6 +3642,8 @@ deparse_simple_command(CollectedCommand *cmd)
case T_CreateStmt:
return deparse_CreateStmt(objectId, parsetree);
+ case T_RenameStmt:
+ return deparse_RenameStmt(cmd->d.simple.address, parsetree);
default:
elog(LOG, "unrecognized node type in deparse command: %d",
@@ -2060,6 +3698,9 @@ deparse_utility_command(CollectedCommand *cmd)
case SCT_Simple:
jsonb = deparse_simple_command(cmd);
break;
+ case SCT_AlterTable:
+ jsonb = deparse_AlterTableStmt(cmd);
+ break;
default:
elog(ERROR, "unexpected deparse node type %d", cmd->type);
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 30b51bf4d3..c0f7f29747 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -2206,6 +2206,23 @@ UtilityContainsQuery(Node *parsetree)
}
}
+/*
+ * stringify_objtype
+ * Return the given object type as a string.
+ */
+const char *
+stringify_objtype(ObjectType objtype)
+{
+ switch (objtype)
+ {
+ case OBJECT_TABLE:
+ return "TABLE";
+ default:
+ elog(ERROR, "unsupported object type %d", objtype);
+ }
+
+ return "???"; /* keep compiler quiet */
+}
/*
* AlterObjectTypeCommandTag
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 59e64aea07..22ce3e1b6f 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -99,6 +99,8 @@ extern Query *UtilityContainsQuery(Node *parsetree);
extern CommandTag CreateCommandTag(Node *parsetree);
+extern const char *stringify_objtype(ObjectType objtype);
+
static inline const char *
CreateCommandName(Node *parsetree)
{
--
2.34.1
[application/octet-stream] 0003-Enhance-the-event-trigger-to-support-DDL--2023_06_22.patch (13.5K, 3-0003-Enhance-the-event-trigger-to-support-DDL--2023_06_22.patch)
download | inline diff:
From cead449ae708edb3648aa5751520b1584fead902 Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Wed, 21 Jun 2023 09:18:49 +0530
Subject: [PATCH 3/5] Enhance the event trigger to support DDL deparsing
ALTER TABLE can have multiple subcommands which might include DROP COLUMN
command and ALTER TYPE referring the drop column in USING expression. As the
dropped column cannot be accessed after the execution of DROP COLUMN, a special
trigger is added to handle this case before the drop column is executed.
---
src/backend/commands/ddldeparse.c | 11 +-
src/backend/commands/event_trigger.c | 188 +++++++++++++++++++++------
src/backend/commands/tablecmds.c | 10 +-
src/include/commands/event_trigger.h | 46 ++++++-
src/include/tcop/deparse_utility.h | 2 +
5 files changed, 204 insertions(+), 53 deletions(-)
diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c
index faaf0de3f9..7f8e416d6a 100644
--- a/src/backend/commands/ddldeparse.c
+++ b/src/backend/commands/ddldeparse.c
@@ -2552,22 +2552,13 @@ deparse_AlterTableStmt(CollectedCommand *cmd)
*/
if (def->raw_default)
{
- Datum deparsed;
- char *defexpr;
- List *exprs = NIL;
-
- exprs = lappend(exprs, def->cooked_default);
- defexpr = nodeToString(def->cooked_default);
- deparsed = DirectFunctionCall2(pg_get_expr,
- CStringGetTextDatum(defexpr),
- RelationGetRelid(rel));
appendStringInfoString(&fmtSub, " %{using}s");
insert_jsonb_key(state, "using");
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 2,
"fmt", jbvString, "USING %{expression}s",
"expression", jbvString,
- TextDatumGetCString(deparsed));
+ sub->usingexpr);
pushJsonbValue(&state, WJB_END_OBJECT, NULL);
}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 4d48e490ed..8c2a494dcb 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -36,6 +36,7 @@
#include "lib/ilist.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
+#include "parser/parser.h"
#include "pgstat.h"
#include "tcop/ddldeparse.h"
#include "tcop/deparse_utility.h"
@@ -49,45 +50,7 @@
#include "utils/rel.h"
#include "utils/syscache.h"
-typedef struct EventTriggerQueryState
-{
- /* memory context for this state's objects */
- MemoryContext cxt;
-
- /* sql_drop */
- slist_head SQLDropList;
- bool in_sql_drop;
-
- /* table_rewrite */
- Oid table_rewrite_oid; /* InvalidOid, or set for table_rewrite
- * event */
- int table_rewrite_reason; /* AT_REWRITE reason */
-
- /* Support for command collection */
- bool commandCollectionInhibited;
- CollectedCommand *currentCommand;
- List *commandList; /* list of CollectedCommand; see
- * deparse_utility.h */
- struct EventTriggerQueryState *previous;
-} EventTriggerQueryState;
-
-static EventTriggerQueryState *currentEventTriggerState = NULL;
-
-/* Support for dropped objects */
-typedef struct SQLDropObject
-{
- ObjectAddress address;
- const char *schemaname;
- const char *objname;
- const char *objidentity;
- const char *objecttype;
- List *addrnames;
- List *addrargs;
- bool original;
- bool normal;
- bool istemp;
- slist_node next;
-} SQLDropObject;
+EventTriggerQueryState *currentEventTriggerState = NULL;
static void AlterEventTriggerOwner_internal(Relation rel,
HeapTuple tup,
@@ -1538,6 +1501,7 @@ EventTriggerAlterTableStart(Node *parsetree)
command->d.alterTable.classId = RelationRelationId;
command->d.alterTable.objectId = InvalidOid;
+ command->d.alterTable.rewrite = false;
command->d.alterTable.subcmds = NIL;
command->parsetree = copyObject(parsetree);
@@ -1571,7 +1535,7 @@ EventTriggerAlterTableRelid(Oid objectId)
* internally, so that's all that this code needs to handle at the moment.
*/
void
-EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
+EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address, bool rewrite)
{
MemoryContext oldcxt;
CollectedATSubcmd *newsub;
@@ -1591,12 +1555,156 @@ EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
newsub->address = address;
newsub->parsetree = copyObject(subcmd);
+ currentEventTriggerState->currentCommand->d.alterTable.rewrite |= rewrite;
currentEventTriggerState->currentCommand->d.alterTable.subcmds =
lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
MemoryContextSwitchTo(oldcxt);
}
+/*
+ * EventTriggerAlterTypeStart
+ * Save data about a single part of an ALTER TYPE.
+ *
+ * ALTER TABLE can have multiple subcommands which might include DROP COLUMN
+ * command and ALTER TYPE referring the drop column in USING expression.
+ * As the dropped column cannot be accessed after the execution of DROP COLUMN,
+ * a special trigger is required to handle this case before the drop column is
+ * executed.
+ */
+void
+EventTriggerAlterTypeStart(AlterTableCmd *subcmd, Relation rel)
+{
+ MemoryContext oldcxt;
+ CollectedATSubcmd *newsub;
+ ColumnDef *def;
+ Relation attrelation;
+ HeapTuple heapTup;
+ Form_pg_attribute attTup;
+ AttrNumber attnum;
+ ObjectAddress address;
+
+ /* ignore if event trigger context not set, or collection disabled */
+ if (!currentEventTriggerState ||
+ currentEventTriggerState->commandCollectionInhibited)
+ return;
+
+ Assert(IsA(subcmd, AlterTableCmd));
+ Assert(subcmd->subtype == AT_AlterColumnType);
+ Assert(currentEventTriggerState->currentCommand != NULL);
+ Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));
+
+ def = (ColumnDef *) subcmd->def;
+ Assert(IsA(def, ColumnDef));
+
+ oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+ newsub = palloc(sizeof(CollectedATSubcmd));
+ newsub->parsetree = (Node *)copyObject(subcmd);
+
+ attrelation = table_open(AttributeRelationId, RowExclusiveLock);
+
+ /* Look up the target column */
+ heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), subcmd->name);
+ if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ subcmd->name, RelationGetRelationName(rel)));
+ attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
+ attnum = attTup->attnum;
+
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
+ heap_freetuple(heapTup);
+ table_close(attrelation, RowExclusiveLock);
+ newsub->address = address;
+
+ if (def->raw_default)
+ {
+ OverrideSearchPath *overridePath;
+ char *defexpr;
+
+ /*
+ * We want all object names to be qualified when deparsing the
+ * expression, so that results are "portable" to environments with
+ * different search_path settings. Rather than inject what would be
+ * repetitive calls to override search path all over the place, we do
+ * it centrally here.
+ */
+ overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+ overridePath->schemas = NIL;
+ overridePath->addCatalog = false;
+ overridePath->addTemp = true;
+ PushOverrideSearchPath(overridePath);
+
+ defexpr = nodeToString(def->cooked_default);
+ newsub->usingexpr = TextDatumGetCString(DirectFunctionCall2(pg_get_expr,
+ CStringGetTextDatum(defexpr),
+ RelationGetRelid(rel)));
+
+ PopOverrideSearchPath();
+ }
+ else
+ newsub->usingexpr = NULL;
+
+ currentEventTriggerState->currentCommand->d.alterTable.subcmds =
+ lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTypeEnd
+ * Finish up saving an ALTER TYPE command, and add it to command list.
+ */
+void
+EventTriggerAlterTypeEnd(Node *subcmd, ObjectAddress address, bool rewrite)
+{
+ MemoryContext oldcxt;
+ CollectedATSubcmd *newsub;
+ ListCell *cell;
+ CollectedCommand *cmd;
+ AlterTableCmd *altsubcmd = (AlterTableCmd *)subcmd;
+
+ /* ignore if event trigger context not set, or collection disabled */
+ if (!currentEventTriggerState ||
+ currentEventTriggerState->commandCollectionInhibited)
+ return;
+
+ cmd = currentEventTriggerState->currentCommand;
+
+ Assert(IsA(subcmd, AlterTableCmd));
+ Assert(cmd != NULL);
+ Assert(OidIsValid(cmd->d.alterTable.objectId));
+
+ foreach(cell, cmd->d.alterTable.subcmds)
+ {
+ CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
+ AlterTableCmd *collcmd = (AlterTableCmd *) sub->parsetree;
+
+ if (collcmd->subtype == altsubcmd->subtype &&
+ address.classId == sub->address.classId &&
+ address.objectId == sub->address.objectId &&
+ address.objectSubId == sub->address.objectSubId)
+ {
+ cmd->d.alterTable.rewrite |= rewrite;
+ return;
+ }
+ }
+
+ oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+ newsub = palloc(sizeof(CollectedATSubcmd));
+ newsub->address = address;
+ newsub->parsetree = copyObject(subcmd);
+
+ cmd->d.alterTable.rewrite |= rewrite;
+ cmd->d.alterTable.subcmds = lappend(cmd->d.alterTable.subcmds, newsub);
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
/*
* EventTriggerAlterTableEnd
* Finish up saving an ALTER TABLE command, and add it to command list.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c15e943f1d..d0e649baaf 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4742,6 +4742,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
AT_PASS_UNSET, context);
Assert(cmd != NULL);
+
+ EventTriggerAlterTypeStart(cmd, rel);
+
/* Performs own recursion */
ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
lockmode, context);
@@ -5013,6 +5016,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
{
ObjectAddress address = InvalidObjectAddress;
Relation rel = tab->rel;
+ bool commandCollected = false;
switch (cmd->subtype)
{
@@ -5136,6 +5140,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
/* parse transformation was done earlier */
address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
+ EventTriggerAlterTypeEnd((Node *) cmd, address, tab->rewrite);
+ commandCollected = true;
break;
case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
address =
@@ -5308,8 +5314,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
/*
* Report the subcommand to interested event triggers.
*/
- if (cmd)
- EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
+ if (cmd && !commandCollected)
+ EventTriggerCollectAlterTableSubcmd((Node *) cmd, address, tab->rewrite);
/*
* Bump the command counter to ensure the next subcommand in the sequence
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 5ed6ece555..28b3486b9e 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -16,6 +16,7 @@
#include "catalog/dependency.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_event_trigger.h"
+#include "lib/ilist.h"
#include "nodes/parsenodes.h"
#include "tcop/cmdtag.h"
#include "tcop/deparse_utility.h"
@@ -29,6 +30,44 @@ typedef struct EventTriggerData
CommandTag tag;
} EventTriggerData;
+typedef struct EventTriggerQueryState
+{
+ /* memory context for this state's objects */
+ MemoryContext cxt;
+
+ /* sql_drop */
+ slist_head SQLDropList;
+ bool in_sql_drop;
+
+ /* table_rewrite */
+ Oid table_rewrite_oid; /* InvalidOid, or set for table_rewrite
+ * event */
+ int table_rewrite_reason; /* AT_REWRITE reason */
+
+ /* Support for command collection */
+ bool commandCollectionInhibited;
+ CollectedCommand *currentCommand;
+ List *commandList; /* list of CollectedCommand; see
+ * deparse_utility.h */
+ struct EventTriggerQueryState *previous;
+} EventTriggerQueryState;
+
+/* Support for dropped objects */
+typedef struct SQLDropObject
+{
+ ObjectAddress address;
+ const char *schemaname;
+ const char *objname;
+ const char *objidentity;
+ const char *objecttype;
+ List *addrnames;
+ List *addrargs;
+ bool original;
+ bool normal;
+ bool istemp;
+ slist_node next;
+} SQLDropObject;
+
#define AT_REWRITE_ALTER_PERSISTENCE 0x01
#define AT_REWRITE_DEFAULT_VAL 0x02
#define AT_REWRITE_COLUMN_REWRITE 0x04
@@ -71,7 +110,12 @@ extern void EventTriggerCollectSimpleCommand(ObjectAddress address,
extern void EventTriggerAlterTableStart(Node *parsetree);
extern void EventTriggerAlterTableRelid(Oid objectId);
extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd,
- ObjectAddress address);
+ ObjectAddress address,
+ bool rewrite);
+
+extern void EventTriggerAlterTypeStart(AlterTableCmd *subcmd, Relation rel);
+extern void EventTriggerAlterTypeEnd(Node *subcmd, ObjectAddress address,
+ bool rewrite);
extern void EventTriggerAlterTableEnd(void);
extern void EventTriggerCollectGrant(InternalGrant *istmt);
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index b585810b9a..1831ec9aae 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -39,6 +39,7 @@ typedef struct CollectedATSubcmd
{
ObjectAddress address; /* affected column, constraint, index, ... */
Node *parsetree;
+ char *usingexpr;
} CollectedATSubcmd;
typedef struct CollectedCommand
@@ -62,6 +63,7 @@ typedef struct CollectedCommand
{
Oid objectId;
Oid classId;
+ bool rewrite;
List *subcmds;
} alterTable;
--
2.34.1
[application/octet-stream] 0001-Deparser-for-Create-And-Drop-Table-DDL-co-2023_06_22.patch (99.4K, 4-0001-Deparser-for-Create-And-Drop-Table-DDL-co-2023_06_22.patch)
download | inline diff:
From 938e02a0ca522706a7244c17cb8f4fbcad57427e Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Mon, 22 May 2023 08:36:49 +0530
Subject: [PATCH 1/5] Deparser for Create And Drop Table DDL commands.
This patch constructs JSON blobs representing DDL commands, which can
later be re-processed into plain strings by well-defined sprintf-like
expansion. These JSON objects are intended to allow for machine-editing of
the commands, by replacing certain nodes within the objects.
Much of the information in the output blob actually comes from system
catalogs, not from the command parse node, as it is impossible to reliably
construct a fully-specified command (i.e. one not dependent on search_path
etc) looking only at the parse node.
This provides a base for logical replication of DDL statements. Currently,
the patch has support for CREATE and DROP TABLE
---
src/backend/commands/Makefile | 2 +
src/backend/commands/ddldeparse.c | 2100 ++++++++++++++++++++++++++
src/backend/commands/ddljson.c | 759 ++++++++++
src/backend/commands/event_trigger.c | 1 +
src/backend/commands/meson.build | 2 +
src/backend/commands/sequence.c | 43 +
src/backend/commands/tablecmds.c | 3 +-
src/backend/parser/parse_utilcmd.c | 1 +
src/backend/utils/adt/format_type.c | 108 +-
src/backend/utils/adt/ruleutils.c | 32 +-
src/include/catalog/pg_proc.dat | 7 +
src/include/commands/sequence.h | 9 +
src/include/commands/tablecmds.h | 2 +
src/include/nodes/parsenodes.h | 1 +
src/include/tcop/ddldeparse.h | 22 +
src/include/utils/builtins.h | 5 +
src/include/utils/ruleutils.h | 10 +
src/tools/pgindent/typedefs.list | 2 +
18 files changed, 3093 insertions(+), 16 deletions(-)
create mode 100644 src/backend/commands/ddldeparse.c
create mode 100644 src/backend/commands/ddljson.c
create mode 100644 src/include/tcop/ddldeparse.h
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 48f7348f91..076ac4eb31 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -29,6 +29,8 @@ OBJS = \
copyto.o \
createas.o \
dbcommands.o \
+ ddldeparse.o \
+ ddljson.o \
define.o \
discard.o \
dropcmds.o \
diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c
new file mode 100644
index 0000000000..269880fbb9
--- /dev/null
+++ b/src/backend/commands/ddldeparse.c
@@ -0,0 +1,2100 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddldeparse.c
+ * Functions to convert utility commands to machine-parseable
+ * representation
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *
+ * This is intended to provide JSON blobs representing DDL commands, which can
+ * later be re-processed into plain strings by well-defined sprintf-like
+ * expansion. These JSON objects are intended to allow for machine-editing of
+ * the commands, by replacing certain nodes within the objects.
+ *
+ * Much of the information in the output blob actually comes from system
+ * catalogs, not from the command parse node, as it is impossible to reliably
+ * construct a fully-specified command (i.e. one not dependent on search_path
+ * etc) looking only at the parse node.
+ *
+ * Deparsed JsonbValue is created by using:
+ * new_jsonb_VA where the key-value pairs composing an jsonb object can be
+ * derived using the passed variable arguments. In order to successfully
+ * construct one key:value pair, a set of three arguments consisting of a name
+ * (string), type (from the jbvType enum) and value must be supplied. It can
+ * take multiple such sets and construct multiple key-value pairs and append
+ * those to output parse-state.
+ *
+ * IDENTIFICATION
+ * src/backend/commands/ddldeparse.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/relation.h"
+#include "access/table.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "commands/tablespace.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "optimizer/optimizer.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/ddldeparse.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
+
+/*
+ * Return the string representation of the given RELPERSISTENCE value.
+ */
+static char *
+get_persistence_str(char persistence)
+{
+ switch (persistence)
+ {
+ case RELPERSISTENCE_TEMP:
+ return "TEMPORARY";
+ case RELPERSISTENCE_UNLOGGED:
+ return "UNLOGGED";
+ case RELPERSISTENCE_PERMANENT:
+ return NULL;
+ default:
+ elog(ERROR, "unexpected persistence marking %c",
+ persistence);
+ return NULL; /* make compiler happy */
+ }
+}
+
+/*
+ * Obtain the deparsed default value for the given column of the given table.
+ *
+ * Caller must have set a correct deparse context and must ensure that the
+ * passed attribute has a default value.
+ */
+static char *
+RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
+{
+ Node *defval;
+ char *defstr;
+
+ defval = build_column_default(rel, attno);
+ Assert(defval != NULL);
+
+ defstr = deparse_expression(defval, dpcontext, false, false);
+
+ return defstr;
+}
+
+/*
+ * Obtain the deparsed partition bound expression for the given table.
+ */
+static char *
+RelationGetPartitionBound(Oid relid)
+{
+ Datum deparsed;
+ Datum bound;
+ bool isnull;
+ HeapTuple tuple;
+
+ tuple = SearchSysCache1(RELOID, relid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation with OID %u", relid);
+
+ bound = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ deparsed = DirectFunctionCall2(pg_get_expr,
+ CStringGetTextDatum(TextDatumGetCString(bound)),
+ relid);
+
+ ReleaseSysCache(tuple);
+
+ return TextDatumGetCString(deparsed);
+}
+
+/*
+ * Insert JsonbValue key to the output parse state.
+ */
+static void
+insert_jsonb_key(JsonbParseState *state, char *name)
+{
+ JsonbValue key;
+
+ /* Push the key */
+ key.type = jbvString;
+ key.val.string.val = name;
+ key.val.string.len = strlen(name);
+ pushJsonbValue(&state, WJB_KEY, &key);
+}
+
+/*
+ * Append new jsonb key:value pairs to the output parse state -- varargs
+ * function
+ *
+ * Arguments:
+ * "state": the output jsonb state where each key-value pair is pushed.
+ *
+ * "numobjs": the number of key:value pairs to be pushed to JsonbParseState;
+ * for each one, a name (string), type (from the jbvType enum) and value must
+ * be supplied. The value must match the type given; for instance, jbvBool
+ * requires an bool, jbvString requires a char * and so on.
+ * Each element type must match the conversion specifier given in the format
+ * string, as described in ddl_deparse_expand_command.
+ *
+ * Notes:
+ * a) The caller can pass "fmt":"fmtstr" as a regular key:value pair to this,
+ * no special handling needed for that.
+ * b) The caller need to carefully pass sets of arguments, we don't have the
+ * luxury of sprintf-like compiler warnings for malformed argument lists.
+ */
+static void
+new_jsonb_VA(JsonbParseState *state, int numobjs,...)
+{
+ va_list args;
+ int i;
+ JsonbValue val;
+
+ /* And process the given varargs */
+ va_start(args, numobjs);
+
+ for (i = 0; i < numobjs; i++)
+ {
+ char *name;
+ enum jbvType type;
+
+ name = va_arg(args, char *);
+ type = va_arg(args, enum jbvType);
+
+ /* Push the key first */
+ insert_jsonb_key(state, name);
+
+ /*
+ * For all param types other than jbvNull, there must be a value in
+ * the varargs. Fetch it and add the fully formed subobject into the
+ * main object.
+ */
+ switch (type)
+ {
+ case jbvNull:
+ /* Null params don't have a value (obviously) */
+ val.type = jbvNull;
+ pushJsonbValue(&state, WJB_VALUE, &val);
+ break;
+
+ case jbvBool:
+ /* Push the bool value */
+ val.type = jbvBool;
+ val.val.boolean = va_arg(args, int);
+ pushJsonbValue(&state, WJB_VALUE, &val);
+ break;
+
+ case jbvString:
+ /* Push the string value */
+ val.type = jbvString;
+ val.val.string.val = pstrdup(va_arg(args, char *));
+ val.val.string.len = strlen(val.val.string.val);
+ pushJsonbValue(&state, WJB_VALUE, &val);
+ break;
+
+ case jbvNumeric:
+ /* Push the numeric value */
+ val.type = jbvNumeric;
+ val.val.numeric = (Numeric)
+ DatumGetNumeric(DirectFunctionCall1(
+ int8_numeric,
+ va_arg(args, int)));
+
+ pushJsonbValue(&state, WJB_VALUE, &val);
+ break;
+
+ default:
+ elog(ERROR, "unrecognized jbvType %d", type);
+ }
+ }
+
+ va_end(args);
+}
+
+/*
+ * A helper routine to insert jsonb for typId to the output parse state.
+ */
+static void
+new_jsonb_for_type(JsonbParseState *state, char *parentKey,
+ Oid typId, int32 typmod)
+{
+ Oid typnspid;
+ char *type_nsp;
+ char *type_name = NULL;
+ char *typmodstr;
+ bool type_array;
+
+ Assert(parentKey);
+
+ format_type_detailed(typId, typmod, &typnspid, &type_name, &typmodstr,
+ &type_array);
+
+ if (OidIsValid(typnspid))
+ type_nsp = get_namespace_name_or_temp(typnspid);
+ else
+ type_nsp = pstrdup("");
+
+ insert_jsonb_key(state, parentKey);
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 4,
+ "schemaname", jbvString, type_nsp,
+ "typename", jbvString, type_name,
+ "typmod", jbvString, typmodstr,
+ "typarray", jbvBool, type_array);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * A helper routine to set up name: schemaname, objname
+ *
+ * Elements "schema_name" and "obj_name" are set. If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the obj_name will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static void
+new_jsonb_for_qualname(JsonbParseState *state, Oid nspid, char *objName,
+ char *keyName, bool createObject)
+{
+ char *namespace;
+
+ if (isAnyTempNamespace(nspid))
+ namespace = pstrdup("pg_temp");
+ else
+ namespace = get_namespace_name(nspid);
+
+ /* Push the key first */
+ if (keyName)
+ insert_jsonb_key(state, keyName);
+
+ if (createObject)
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 2,
+ "schemaname", jbvString, namespace,
+ "objname", jbvString, objName);
+
+ if (createObject)
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * A helper routine to set up name: 'schemaname, objname' where the object is
+ * specified by classId and objId.
+ */
+static void
+new_jsonb_for_qualname_id(JsonbParseState *state, Oid classId, Oid objectId,
+ char *keyName, bool createObject)
+{
+ Relation catalog;
+ HeapTuple catobj;
+ Datum obj_nsp;
+ Datum obj_name;
+ AttrNumber Anum_name;
+ AttrNumber Anum_namespace;
+ AttrNumber Anum_oid = get_object_attnum_oid(classId);
+ bool isnull;
+
+ catalog = table_open(classId, AccessShareLock);
+
+ catobj = get_catalog_object_by_oid(catalog, Anum_oid, objectId);
+ if (!catobj)
+ elog(ERROR, "cache lookup failed for object with OID %u of catalog \"%s\"",
+ objectId, RelationGetRelationName(catalog));
+ Anum_name = get_object_attnum_name(classId);
+ Anum_namespace = get_object_attnum_namespace(classId);
+
+ obj_nsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null namespace for object %u", objectId);
+
+ obj_name = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null attribute name for object %u", objectId);
+
+ new_jsonb_for_qualname(state, DatumGetObjectId(obj_nsp),
+ NameStr(*DatumGetName(obj_name)),
+ keyName, createObject);
+ table_close(catalog, AccessShareLock);
+}
+
+/*
+ * A helper routine to insert key:value where value is array of qualname to
+ * the output parse state.
+ */
+static void
+new_jsonbArray_for_qualname_id(JsonbParseState *state,
+ char *keyname, List *array)
+{
+ ListCell *lc;
+
+ /* Push the key first */
+ insert_jsonb_key(state, keyname);
+
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+ /* Push the array elements now */
+ foreach(lc, array)
+ new_jsonb_for_qualname_id(state, RelationRelationId, lfirst_oid(lc),
+ NULL, true);
+
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+}
+
+/*
+ * A helper routine to insert collate object for column
+ * definition to the output parse state.
+ */
+static void
+insert_collate_object(JsonbParseState *state, char *parentKey, char *fmt,
+ Oid classId, Oid objectId, char *key)
+{
+ /*
+ * Insert parent key for which we are going to create value object here.
+ */
+ if (parentKey)
+ insert_jsonb_key(state, parentKey);
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmt);
+
+ /* push object now */
+ new_jsonb_for_qualname_id(state, classId, objectId, key, true);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * A helper routine to insert identity object for the table definition
+ * to the output parse state.
+ */
+static void
+insert_identity_object(JsonbParseState *state, Oid nspid, char *relname)
+{
+ new_jsonb_for_qualname(state, nspid, relname, "identity", true);
+}
+
+/*
+ * Deparse the sequence CACHE option to Jsonb
+ *
+ * Verbose syntax
+ * CACHE %{value}
+ */
+static inline void
+deparse_Seq_Cache_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "CACHE %{value}s",
+ "clause", jbvString, "cache",
+ "value", jbvString,
+ psprintf(INT64_FORMAT, seqdata->seqcache));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence CYCLE option to Jsonb.
+ *
+ * Verbose syntax
+ * %{no}s CYCLE
+ */
+static inline void
+deparse_Seq_Cycle_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "%{no}s CYCLE",
+ "clause", jbvString, "cycle",
+ "no", jbvString, seqdata->seqcycle ?
+ "" : "NO");
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence INCREMENT BY option to Jsonb
+ *
+ * Verbose syntax
+ * INCREMENT BY %{value}s
+ */
+static inline void
+deparse_Seq_IncrementBy_toJsonb(JsonbParseState *state,
+ Form_pg_sequence seqdata)
+{
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "INCREMENT BY %{value}s",
+ "clause", jbvString, "seqincrement",
+ "value", jbvString,
+ psprintf(INT64_FORMAT, seqdata->seqincrement));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence MAXVALUE option to Jsonb.
+ *
+ * Verbose syntax
+ * MAXVALUE %{value}s
+ */
+static inline void
+deparse_Seq_Maxvalue_toJsonb(JsonbParseState *state,
+ Form_pg_sequence seqdata)
+{
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "MAXVALUE %{value}s",
+ "clause", jbvString, "maxvalue",
+ "value", jbvString,
+ psprintf(INT64_FORMAT, seqdata->seqmax));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence MINVALUE option to Jsonb
+ *
+ * Verbose syntax
+ * MINVALUE %{value}s
+ */
+static inline void
+deparse_Seq_Minvalue_toJsonb(JsonbParseState *state,
+ Form_pg_sequence seqdata)
+{
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "MINVALUE %{value}s",
+ "clause", jbvString, "minvalue",
+ "value", jbvString,
+ psprintf(INT64_FORMAT, seqdata->seqmin));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence OWNED BY command.
+ *
+ * Verbose syntax
+ * OWNED BY %{owner}D
+ */
+static void
+deparse_Seq_OwnedBy_toJsonb(JsonbParseState *state, Oid sequenceId)
+{
+ Relation depRel;
+ SysScanDesc scan;
+ ScanKeyData keys[3];
+ HeapTuple tuple;
+ bool elem_found PG_USED_FOR_ASSERTS_ONLY = false;
+
+ depRel = table_open(DependRelationId, AccessShareLock);
+ ScanKeyInit(&keys[0],
+ Anum_pg_depend_classid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationRelationId));
+ ScanKeyInit(&keys[1],
+ Anum_pg_depend_objid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(sequenceId));
+ ScanKeyInit(&keys[2],
+ Anum_pg_depend_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(0));
+
+ scan = systable_beginscan(depRel, DependDependerIndexId, true,
+ NULL, 3, keys);
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Oid ownerId;
+ Form_pg_depend depform;
+ char *colname;
+
+ depform = (Form_pg_depend) GETSTRUCT(tuple);
+
+ /* Only consider AUTO dependencies on pg_class */
+ if (depform->deptype != DEPENDENCY_AUTO)
+ continue;
+ if (depform->refclassid != RelationRelationId)
+ continue;
+ if (depform->refobjsubid <= 0)
+ continue;
+
+ ownerId = depform->refobjid;
+ colname = get_attname(ownerId, depform->refobjsubid, false);
+
+ /* mark the begin of owner's definition object */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "OWNED BY %{owner}D",
+ "clause", jbvString, "owned");
+
+ /* owner key */
+ insert_jsonb_key(state, "owner");
+
+ /* owner value begin */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_for_qualname_id(state, RelationRelationId,
+ ownerId, NULL, false);
+ new_jsonb_VA(state, 1, "attrname", jbvString, colname);
+
+ /* owner value end */
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ /* mark the end of owner's definition object */
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+#ifdef USE_ASSERT_CHECKING
+ elem_found = true;
+#endif
+ }
+
+ systable_endscan(scan);
+ relation_close(depRel, AccessShareLock);
+
+ /*
+ * If there's no owner column, assert. The caller must have checked
+ * presence of owned_by element before invoking this.
+ */
+ Assert(elem_found);
+}
+
+/*
+ * Deparse the sequence START WITH option to Jsonb.
+ *
+ * Verbose syntax
+ * START WITH %{value}s
+ */
+static inline void
+deparse_Seq_Startwith_toJsonb(JsonbParseState *state,
+ Form_pg_sequence seqdata)
+{
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "START WITH %{value}s",
+ "clause", jbvString, "start",
+ "value", jbvString,
+ psprintf(INT64_FORMAT, seqdata->seqstart));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence RESTART option to Jsonb
+ *
+ * Verbose syntax
+ * RESTART %{value}s
+ */
+static inline void
+deparse_Seq_Restart_toJsonb(JsonbParseState *state, int64 last_value)
+{
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 3,
+ "fmt", jbvString, "RESTART %{value}s",
+ "clause", jbvString, "restart",
+ "value", jbvString,
+ psprintf(INT64_FORMAT, last_value));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the sequence AS option.
+ *
+ * Verbose syntax
+ * AS %{seqtype}T
+ */
+static inline void
+deparse_Seq_As_toJsonb(JsonbParseState *state, Form_pg_sequence seqdata)
+{
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 1, "fmt", jbvString, "AS %{seqtype}T");
+ new_jsonb_for_type(state, "seqtype", seqdata->seqtypid, -1);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse the definition of column identity to Jsonb.
+ *
+ * Verbose syntax
+ * GENERATED %{option}s AS IDENTITY %{identity_type}s ( %{seq_definition: }s )
+ */
+static void
+deparse_ColumnIdentity_toJsonb(JsonbParseState *state, char *parentKey,
+ Oid seqrelid, char identity)
+{
+ Form_pg_sequence seqform;
+ Sequence_values *seqvalues;
+ StringInfoData fmtStr;
+
+ initStringInfo(&fmtStr);
+
+ /*
+ * Insert parent key for which we are going to create value object here.
+ */
+ if (parentKey)
+ insert_jsonb_key(state, parentKey);
+
+ /* create object now for value of identity_column */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ /* identity_type object creation */
+ if (identity == ATTRIBUTE_IDENTITY_ALWAYS ||
+ identity == ATTRIBUTE_IDENTITY_BY_DEFAULT)
+ {
+ appendStringInfoString(&fmtStr, "%{identity_type}s");
+ insert_jsonb_key(state, "identity_type");
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString,
+ "GENERATED %{option}s AS IDENTITY",
+ "option", jbvString,
+ (identity == ATTRIBUTE_IDENTITY_ALWAYS ?
+ "ALWAYS" : "BY DEFAULT"));
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ /* seq_definition array object creation */
+ insert_jsonb_key(state, "seq_definition");
+
+ appendStringInfoString(&fmtStr, " ( %{seq_definition: }s )");
+
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+ seqvalues = get_sequence_values(seqrelid);
+ seqform = seqvalues->seqform;
+
+ /* Definition elements */
+ deparse_Seq_Cache_toJsonb(state, seqform);
+ deparse_Seq_Cycle_toJsonb(state, seqform);
+ deparse_Seq_IncrementBy_toJsonb(state, seqform);
+ deparse_Seq_Minvalue_toJsonb(state, seqform);
+ deparse_Seq_Maxvalue_toJsonb(state, seqform);
+ deparse_Seq_Startwith_toJsonb(state, seqform);
+ deparse_Seq_Restart_toJsonb(state, seqvalues->last_value);
+ /* We purposefully do not emit OWNED BY here */
+
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+ pfree(fmtStr.data);
+
+ /* end of idendity_column object */
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse a ColumnDef node within a regular (non-typed) table creation.
+ *
+ * NOT NULL constraints in the column definition are emitted directly in the
+ * column definition by this routine; other constraints must be emitted
+ * elsewhere (the info in the parse node is incomplete anyway).
+ *
+ * Verbose syntax
+ * "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s
+ * %{not_null}s %{default}s %{identity_column}s %{generated_column}s"
+ */
+static void
+deparse_ColumnDef_toJsonb(JsonbParseState *state, Relation relation,
+ List *dpcontext, bool composite, ColumnDef *coldef)
+{
+ Oid relid = RelationGetRelid(relation);
+ HeapTuple attrTup;
+ Form_pg_attribute attrForm;
+ Oid typid;
+ int32 typmod;
+ Oid typcollation;
+ bool saw_notnull;
+ ListCell *cell;
+ StringInfoData fmtStr;
+
+ initStringInfo(&fmtStr);
+
+ /*
+ * Inherited columns without local definitions should be skipped. We don't
+ * want those to be part of final string.
+ */
+ if (!coldef->is_local)
+ return;
+
+ attrTup = SearchSysCacheAttName(relid, coldef->colname);
+ if (!HeapTupleIsValid(attrTup))
+ elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
+ coldef->colname, relid);
+ attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
+
+ get_atttypetypmodcoll(relid, attrForm->attnum,
+ &typid, &typmod, &typcollation);
+
+ /* start making column object */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ /* create name and type elements for column */
+ appendStringInfoString(&fmtStr, "%{name}I");
+ new_jsonb_VA(state, 2,
+ "name", jbvString, coldef->colname,
+ "type", jbvString, "column");
+
+ /*
+ * create coltype object having 4 elements: schemaname, typename, typemod,
+ * typearray
+ */
+ appendStringInfoString(&fmtStr, " %{coltype}T");
+ new_jsonb_for_type(state, "coltype", typid, typmod);
+
+ /* STORAGE clause */
+ if (!composite)
+ {
+ appendStringInfoString(&fmtStr, " STORAGE %{colstorage}s");
+ new_jsonb_VA(state, 1,
+ "colstorage", jbvString,
+ storage_name(attrForm->attstorage));
+ }
+
+ /* COMPRESSION clause */
+ if (coldef->compression)
+ {
+ appendStringInfoString(&fmtStr, " %{compression}s");
+ insert_jsonb_key(state, "compression");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "COMPRESSION %{compression_method}I",
+ "compression_method", jbvString, coldef->compression);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ /* COLLATE clause */
+ if (OidIsValid(typcollation))
+ {
+ appendStringInfoString(&fmtStr, " %{collation}s");
+ insert_collate_object(state, "collation",
+ "COLLATE %{collation_name}D",
+ CollationRelationId, typcollation,
+ "collation_name");
+ }
+
+ if (!composite)
+ {
+ Oid seqrelid = InvalidOid;
+
+ /*
+ * Emit a NOT NULL declaration if necessary. Note that we cannot
+ * trust pg_attribute.attnotnull here, because that bit is also set
+ * when primary keys are specified; we must not emit a NOT NULL
+ * constraint in that case, unless explicitly specified. Therefore,
+ * we scan the list of constraints attached to this column to
+ * determine whether we need to emit anything. (Fortunately, NOT NULL
+ * constraints cannot be table constraints.)
+ *
+ * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is
+ * marked is_not_null.
+ */
+ saw_notnull = false;
+ foreach(cell, coldef->constraints)
+ {
+ Constraint *constr = (Constraint *) lfirst(cell);
+
+ if (constr->contype == CONSTR_NOTNULL)
+ {
+ saw_notnull = true;
+ break;
+ }
+ }
+
+ /* NOT NULL */
+ if (saw_notnull)
+ {
+ appendStringInfoString(&fmtStr, " %{not_null}s");
+ new_jsonb_VA(state, 1,
+ "not_null", jbvString, "NOT NULL");
+ }
+
+
+ /* DEFAULT */
+ if (attrForm->atthasdef &&
+ coldef->generated != ATTRIBUTE_GENERATED_STORED)
+ {
+ char *defstr;
+
+ appendStringInfoString(&fmtStr, " %{default}s");
+
+ defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+ dpcontext);
+
+ insert_jsonb_key(state, "default");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "DEFAULT %{default}s",
+ "default", jbvString, defstr);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ /* IDENTITY COLUMN */
+ if (coldef->identity)
+ {
+ /*
+ * For identity column, find the sequence owned by column in order
+ * to deparse the column definition.
+ */
+ seqrelid = getIdentitySequence(relid, attrForm->attnum, true);
+ if (OidIsValid(seqrelid) && coldef->identitySequence)
+ seqrelid = RangeVarGetRelid(coldef->identitySequence,
+ NoLock, false);
+
+ if (OidIsValid(seqrelid))
+ {
+ appendStringInfoString(&fmtStr, " %{identity_column}s");
+ deparse_ColumnIdentity_toJsonb(state, "identity_column",
+ seqrelid,
+ coldef->identity);
+ }
+ }
+
+
+ /* GENERATED COLUMN EXPRESSION */
+ if (coldef->generated == ATTRIBUTE_GENERATED_STORED)
+ {
+ char *defstr;
+
+ appendStringInfoString(&fmtStr, " %{generated_column}s");
+ defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+ dpcontext);
+
+ insert_jsonb_key(state, "generated_column");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "GENERATED ALWAYS AS"
+ " (%{generation_expr}s) STORED",
+ "generation_expr", jbvString, defstr);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+ }
+
+ ReleaseSysCache(attrTup);
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+ pfree(fmtStr.data);
+
+ /* mark the end of one column object */
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Helper for deparse_ColumnDef_typed_toJsonb()
+ *
+ * Returns true if we need to deparse a ColumnDef node within a typed
+ * table creation.
+ */
+static bool
+deparse_ColDef_typed_needed(Relation relation, ColumnDef *coldef,
+ Form_pg_attribute *atFormOut, bool *notnull)
+{
+ Oid relid = RelationGetRelid(relation);
+ HeapTuple attrTup;
+ Form_pg_attribute attrForm;
+ Oid typid;
+ int32 typmod;
+ Oid typcollation;
+ bool saw_notnull;
+ ListCell *cell;
+
+ attrTup = SearchSysCacheAttName(relid, coldef->colname);
+ if (!HeapTupleIsValid(attrTup))
+ elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
+ coldef->colname, relid);
+
+ attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
+
+ if (atFormOut)
+ *atFormOut = attrForm;
+
+ get_atttypetypmodcoll(relid, attrForm->attnum,
+ &typid, &typmod, &typcollation);
+
+ /*
+ * Search for a NOT NULL declaration. As in deparse_ColumnDef, we rely on
+ * finding a constraint on the column rather than coldef->is_not_null.
+ * (This routine is never used for ALTER cases.)
+ */
+ saw_notnull = false;
+ foreach(cell, coldef->constraints)
+ {
+ Constraint *constr = (Constraint *) lfirst(cell);
+
+ if (constr->contype == CONSTR_NOTNULL)
+ {
+ saw_notnull = true;
+ break;
+ }
+ }
+
+ if (notnull)
+ *notnull = saw_notnull;
+
+ if (!saw_notnull && !attrForm->atthasdef)
+ {
+ ReleaseSysCache(attrTup);
+ return false;
+ }
+
+ ReleaseSysCache(attrTup);
+ return true;
+}
+
+/*
+ * Deparse a ColumnDef node within a typed table creation. This is simpler
+ * than the regular case, because we don't have to emit the type declaration,
+ * collation, or default. Here we only return something if the column is being
+ * declared NOT NULL.
+ *
+ * As in deparse_ColumnDef, any other constraint is processed elsewhere.
+ *
+ * Verbose syntax
+ * %{name}I WITH OPTIONS %{not_null}s %{default}s.
+ */
+static void
+deparse_ColumnDef_typed_toJsonb(JsonbParseState *state, Relation relation,
+ List *dpcontext, ColumnDef *coldef)
+{
+ bool needed;
+ Form_pg_attribute attrForm;
+ bool saw_notnull;
+ StringInfoData fmtStr;
+
+ initStringInfo(&fmtStr);
+
+ needed = deparse_ColDef_typed_needed(relation, coldef,
+ &attrForm, &saw_notnull);
+ if (!needed)
+ return;
+
+ /* start making column object */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ appendStringInfoString(&fmtStr, "%{name}I WITH OPTIONS");
+
+ /* TYPE and NAME */
+ new_jsonb_VA(state, 2,
+ "type", jbvString, "column",
+ "name", jbvString, coldef->colname);
+
+ /* NOT NULL */
+ if (saw_notnull)
+ {
+ appendStringInfoString(&fmtStr, " %{not_null}s");
+ new_jsonb_VA(state, 1, "not_null", jbvString, "NOT NULL");
+ }
+
+ /* DEFAULT */
+ if (attrForm->atthasdef)
+ {
+ char *defstr;
+
+ appendStringInfoString(&fmtStr, " %{default}s");
+ defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+ dpcontext);
+
+ insert_jsonb_key(state, "default");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "DEFAULT %{default}s",
+ "default", jbvString, defstr);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+ pfree(fmtStr.data);
+
+ /* mark the end of column object */
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ /* Generated columns are not supported on typed tables, so we are done */
+}
+
+/*
+ * Helper routine for table_elem_present()
+ *
+ * Returns true if at-least one local column is present
+ * (i.e. not inherited).
+ */
+static bool
+table_elem_present_col(Relation relation, List *tableElements, bool typed)
+{
+ ListCell *lc;
+
+ foreach(lc, tableElements)
+ {
+ Node *elt = (Node *) lfirst(lc);
+
+ switch (nodeTag(elt))
+ {
+ case T_ColumnDef:
+ {
+ if (typed)
+ {
+ if (deparse_ColDef_typed_needed(relation,
+ ((ColumnDef *) elt),
+ NULL, NULL))
+ return true;
+ }
+ else
+ {
+ if (((ColumnDef *) elt)->is_local)
+ return true;
+ }
+ }
+ break;
+ case T_Constraint:
+ break;
+ default:
+ elog(ERROR, "invalid node type %d", nodeTag(elt));
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Helper routine for table_elem_present()
+ *
+ * Returns true if at-least one constraint is present.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static bool
+table_elem_present_const(Oid relationId)
+{
+ Relation conRel;
+ ScanKeyData key;
+ SysScanDesc scan;
+ HeapTuple tuple;
+ bool bconst = false;
+
+ Assert(OidIsValid(relationId));
+
+ /*
+ * Scan pg_constraint to fetch all constraints linked to the given
+ * relation.
+ */
+ conRel = table_open(ConstraintRelationId, AccessShareLock);
+ ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(relationId));
+ scan = systable_beginscan(conRel, ConstraintRelidTypidNameIndexId, true,
+ NULL, 1, &key);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint constrForm;
+
+ constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+
+ switch (constrForm->contype)
+ {
+ case CONSTRAINT_CHECK:
+ case CONSTRAINT_PRIMARY:
+ case CONSTRAINT_UNIQUE:
+ case CONSTRAINT_EXCLUSION:
+ /* No need to deparse constraints inherited from parent table */
+ if (OidIsValid(constrForm->conparentid))
+ continue;
+
+ bconst = true;
+ break;
+ case CONSTRAINT_FOREIGN:
+ continue; /* not here */
+ default:
+ elog(ERROR, "unrecognized constraint type");
+ }
+ }
+
+ systable_endscan(scan);
+ table_close(conRel, AccessShareLock);
+
+ return bconst;
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Returns true if at-least one local column or constraint is present.
+ */
+static bool
+table_elem_present(Oid relationId, Relation relation,
+ List *tableElements, bool typed)
+{
+ bool telems_present = false;
+
+ telems_present = table_elem_present_col(relation, tableElements, typed);
+
+ if (!telems_present)
+ telems_present = table_elem_present_const(relationId);
+
+ return telems_present;
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Deal with all the table elements (columns and constraints).
+ *
+ * Note we ignore constraints in the parse node here; they are extracted from
+ * system catalogs instead.
+ */
+static void
+deparse_TableElems_ToJsonb(JsonbParseState *state, Relation relation,
+ List *tableElements, List *dpcontext,
+ bool typed, bool composite)
+{
+ ListCell *lc;
+
+ foreach(lc, tableElements)
+ {
+ Node *elt = (Node *) lfirst(lc);
+
+ switch (nodeTag(elt))
+ {
+ case T_ColumnDef:
+ {
+ if (typed)
+ deparse_ColumnDef_typed_toJsonb(state, relation,
+ dpcontext,
+ (ColumnDef *) elt);
+ else
+ deparse_ColumnDef_toJsonb(state, relation, dpcontext,
+ composite, (ColumnDef *) elt);
+ }
+ break;
+ case T_Constraint:
+ break;
+ default:
+ elog(ERROR, "invalid node type %d", nodeTag(elt));
+ }
+ }
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Deparse the INHERITS relations.
+ *
+ * Given a table OID, return a schema-qualified table list representing
+ * the parent tables.
+ */
+static List *
+deparse_InhRels_ToJsonb(Oid objectId)
+{
+ List *parents = NIL;
+ Relation inhRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+
+ inhRel = table_open(InheritsRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objectId));
+
+ scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ parents = lappend_oid(parents, formInh->inhparent);
+ }
+
+ systable_endscan(scan);
+ table_close(inhRel, RowExclusiveLock);
+
+ return parents;
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Given a table OID, obtain its constraints and append them to the given
+ * JsonbParseState.
+ *
+ * This works for typed tables, regular tables.
+ *
+ * Note that CONSTRAINT_FOREIGN constraints are always ignored.
+ */
+static void
+deparse_Constraints_ToJsonb(JsonbParseState *state, Oid relationId)
+{
+ Relation conRel;
+ ScanKeyData key;
+ SysScanDesc scan;
+ HeapTuple tuple;
+
+ Assert(OidIsValid(relationId));
+
+ /*
+ * Scan pg_constraint to fetch all constraints linked to the given
+ * relation.
+ */
+ conRel = table_open(ConstraintRelationId, AccessShareLock);
+ ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(relationId));
+ scan = systable_beginscan(conRel, ConstraintRelidTypidNameIndexId, true,
+ NULL, 1, &key);
+
+ /*
+ * For each constraint, add a node to the list of table elements. In
+ * these nodes we include not only the printable information ("fmt"), but
+ * also separate attributes to indicate the type of constraint, for
+ * automatic processing.
+ */
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint constrForm;
+ char *contype;
+ StringInfoData fmtStr;
+
+ constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+
+ switch (constrForm->contype)
+ {
+ case CONSTRAINT_CHECK:
+ contype = "check";
+ break;
+ case CONSTRAINT_FOREIGN:
+ continue; /* not here */
+ case CONSTRAINT_PRIMARY:
+ contype = "primary key";
+ break;
+ case CONSTRAINT_UNIQUE:
+ contype = "unique";
+ break;
+ case CONSTRAINT_EXCLUSION:
+ contype = "exclusion";
+ break;
+ default:
+ elog(ERROR, "unrecognized constraint type");
+ }
+
+ /* No need to deparse constraints inherited from parent table. */
+ if (OidIsValid(constrForm->conparentid))
+ continue;
+
+ /*
+ * "type" and "contype" are not part of the printable output, but are
+ * useful to programmatically distinguish these from columns and among
+ * different constraint types.
+ *
+ * XXX it might be useful to also list the column names in a PK, etc.
+ */
+ initStringInfo(&fmtStr);
+ appendStringInfoString(&fmtStr, "CONSTRAINT %{name}I %{definition}s");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 4,
+ "type", jbvString, "constraint",
+ "contype", jbvString, contype,
+ "name", jbvString, NameStr(constrForm->conname),
+ "definition", jbvString,
+ pg_get_constraintdef_string(constrForm->oid));
+
+ if (constrForm->conindid &&
+ (constrForm->contype == CONSTRAINT_PRIMARY ||
+ constrForm->contype == CONSTRAINT_UNIQUE ||
+ constrForm->contype == CONSTRAINT_EXCLUSION))
+ {
+ Oid tblspc = get_rel_tablespace(constrForm->conindid);
+
+ if (OidIsValid(tblspc))
+ {
+ char *tblspcname = get_tablespace_name(tblspc);
+
+ if (!tblspcname)
+ elog(ERROR, "cache lookup failed for tablespace %u",
+ tblspc);
+
+ appendStringInfoString(&fmtStr,
+ " USING INDEX TABLESPACE %{tblspc}s");
+ new_jsonb_VA(state, 1,
+ "tblspc", jbvString, tblspcname);
+ }
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+ pfree(fmtStr.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ systable_endscan(scan);
+ table_close(conRel, AccessShareLock);
+}
+
+/*
+ * Subroutine for CREATE TABLE deparsing.
+ *
+ * Insert columns and constraints elements in output JsonbParseState
+ * Returns true if elements are inserted
+ */
+static bool
+add_table_elems_if_any(JsonbParseState *state, StringInfo fmtStr,
+ Relation relation, List *tableElts, List *dpcontext,
+ Oid objectId, bool typed, bool composite)
+{
+ bool telems_present = false;
+
+ telems_present = table_elem_present(objectId, relation, tableElts, typed);
+
+ if (telems_present)
+ {
+ appendStringInfoString(fmtStr, " (%{table_elements:, }s)");
+ insert_jsonb_key(state, "table_elements");
+
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+ /*
+ * Process table elements: column definitions and constraints. Only
+ * the column definitions are obtained from the parse node itself. To
+ * get constraints we rely on pg_constraint, because the parse node
+ * might be missing some things such as the name of the constraints.
+ */
+ deparse_TableElems_ToJsonb(state, relation, tableElts, dpcontext,
+ typed, /* typed table */
+ composite); /* not composite */
+
+ deparse_Constraints_ToJsonb(state, objectId);
+
+ /* end of table_elements array */
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+ }
+
+ return telems_present;
+}
+
+/*
+ * Deparse DefElems, as used by Create Table
+ *
+ * Verbose syntax
+ * %{label}s = %{value}L
+ */
+static void
+deparse_DefElem_ToJsonb(JsonbParseState *state, DefElem *elem, bool is_reset)
+{
+ StringInfoData fmtStr;
+ StringInfoData labelfmt;
+
+ initStringInfo(&fmtStr);
+
+ appendStringInfoString(&fmtStr, "%{label}s");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ /* LABEL */
+ initStringInfo(&labelfmt);
+
+ insert_jsonb_key(state, "label");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ if (elem->defnamespace != NULL)
+ {
+ appendStringInfoString(&labelfmt, "%{schema}I.");
+ new_jsonb_VA(state, 1,
+ "schema", jbvString, elem->defnamespace);
+ }
+
+ appendStringInfoString(&labelfmt, "%{label}I");
+ new_jsonb_VA(state, 1,
+ "label", jbvString, elem->defname);
+
+ new_jsonb_VA(state, 1, "fmt", jbvString, labelfmt.data);
+ pfree(labelfmt.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ /* VALUE */
+ if (!is_reset)
+ {
+ appendStringInfoString(&fmtStr, " = %{value}L");
+ new_jsonb_VA(state, 1, "value", jbvString,
+ elem->arg ? defGetString(elem) :
+ defGetBoolean(elem) ? "true" : "false");
+ }
+
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+ pfree(fmtStr.data);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+}
+
+/*
+ * Deparse WITH clause, as used by Create Table.
+ */
+static void
+deparse_withObj_ToJsonb(JsonbParseState *state, CreateStmt *node)
+{
+ ListCell *cell;
+
+ /* WITH */
+ insert_jsonb_key(state, "with");
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+ /* add elements to array */
+ foreach(cell, node->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(cell);
+
+ deparse_DefElem_ToJsonb(state, opt, false);
+ }
+
+ /* with's array end */
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+}
+
+/*
+ * Deparse a CreateStmt (CREATE TABLE).
+ *
+ * Given a table OID and the parse tree that created it, return JsonbValue
+ * representing the creation command.
+ *
+ * Verbose syntax
+ * CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D [OF
+ * %{of_type}T | PARTITION OF %{parent_identity}D] %{table_elements}s
+ * %{inherits}s %{partition_by}s %{access_method}s %{with_clause}s
+ * %{tablespace}s
+ */
+static Jsonb *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+ CreateStmt *node = (CreateStmt *) parsetree;
+ Relation relation = relation_open(objectId, AccessShareLock);
+ Oid nspid = relation->rd_rel->relnamespace;
+ char *relname = RelationGetRelationName(relation);
+ List *dpcontext;
+ char *perstr;
+ StringInfoData fmtStr;
+ JsonbParseState *state = NULL;
+ JsonbValue *value;
+
+ initStringInfo(&fmtStr);
+
+ /* mark the begin of ROOT object and start adding elements to it. */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ appendStringInfoString(&fmtStr, "CREATE");
+
+ /* PERSISTENCE */
+ perstr = get_persistence_str(relation->rd_rel->relpersistence);
+ if (perstr)
+ {
+ appendStringInfoString(&fmtStr, " %{persistence}s");
+ new_jsonb_VA(state, 1,
+ "persistence", jbvString, perstr);
+ }
+
+ appendStringInfoString(&fmtStr, " TABLE");
+
+ /* IF NOT EXISTS */
+ if (node->if_not_exists)
+ {
+ appendStringInfoString(&fmtStr, " %{if_not_exists}s");
+ new_jsonb_VA(state, 1,
+ "if_not_exists", jbvString, "IF NOT EXISTS");
+ }
+
+ /* IDENTITY */
+ appendStringInfoString(&fmtStr, " %{identity}D");
+ insert_identity_object(state, nspid, relname);
+
+ dpcontext = deparse_context_for(RelationGetRelationName(relation),
+ objectId);
+
+ /*
+ * TABLE-ELEMENTS array creation
+ *
+ * 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)
+ {
+ /* Insert the "of type" or "partition of" clause whichever present */
+ if (node->ofTypename)
+ {
+ appendStringInfoString(&fmtStr, " OF %{of_type}T");
+ new_jsonb_for_type(state, "of_type",
+ relation->rd_rel->reloftype, -1);
+ }
+ else
+ {
+ List *parents;
+ Oid objid;
+
+ appendStringInfoString(&fmtStr, " PARTITION OF %{parent_identity}D");
+ parents = deparse_InhRels_ToJsonb(objectId);
+ objid = linitial_oid(parents);
+ Assert(list_length(parents) == 1);
+ new_jsonb_for_qualname_id(state, RelationRelationId,
+ objid, "parent_identity", true);
+ }
+
+ /*
+ * 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 first check the presence and then add
+ * the elements.
+ */
+ add_table_elems_if_any(state, &fmtStr, relation,
+ node->tableElts, dpcontext, objectId,
+ true, /* typed table */
+ false); /* not composite */
+ }
+ else
+ {
+ List *inhrelations;
+ bool telems_present = false;
+
+ /*
+ * There is no need to process LIKE clauses separately; they have
+ * already been transformed into columns and constraints.
+ */
+
+ /* Check if table elements are present, if so, add them. */
+ telems_present = add_table_elems_if_any(state, &fmtStr, relation,
+ node->tableElts, dpcontext, objectId,
+ false, /* not typed table */
+ false); /* not composite */
+
+ /*
+ * If no table elements added, then add empty "()" needed for
+ * 'inherit' create table syntax. Example: CREATE TABLE t1 () INHERITS
+ * (t0);
+ */
+ if (!telems_present)
+ appendStringInfoString(&fmtStr, " ()");
+
+ /*
+ * Add inheritance specification. We cannot simply scan the list of
+ * parents from the parser node, because that may lack the actual
+ * qualified names of the parent relations. Rather than trying to
+ * re-resolve them from the information in the parse node, it seems
+ * more accurate and convenient to grab it from pg_inherits.
+ */
+ if (node->inhRelations != NIL)
+ {
+ appendStringInfoString(&fmtStr, " %{inherits}s");
+ insert_jsonb_key(state, "inherits");
+
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 1, "fmt", jbvString, "INHERITS (%{parents:, }D)");
+ inhrelations = deparse_InhRels_ToJsonb(objectId);
+
+ new_jsonbArray_for_qualname_id(state, "parents", inhrelations);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+ }
+
+ /* FOR VALUES clause */
+ if (node->partbound)
+ {
+ appendStringInfoString(&fmtStr, " %{partition_bound}s");
+
+ /*
+ * Get pg_class.relpartbound. We cannot use partbound in the parsetree
+ * directly as it's the original partbound expression which haven't
+ * been transformed.
+ */
+ new_jsonb_VA(state, 1,
+ "partition_bound", jbvString,
+ RelationGetPartitionBound(objectId));
+ }
+
+ /* PARTITION BY clause */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ appendStringInfoString(&fmtStr, " %{partition_by}s");
+ insert_jsonb_key(state, "partition_by");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "PARTITION BY %{definition}s",
+ "definition", jbvString,
+ pg_get_partkeydef_string(objectId));
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ /* USING clause */
+ if (node->accessMethod)
+ {
+ appendStringInfoString(&fmtStr, " %{access_method}s");
+ insert_jsonb_key(state, "access_method");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "USING %{access_method}I",
+ "access_method", jbvString, node->accessMethod);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ /* WITH clause */
+ if (node->options)
+ {
+ appendStringInfoString(&fmtStr, " %{with_clause}s");
+ insert_jsonb_key(state, "with_clause");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 1,
+ "fmt", jbvString, "WITH (%{with:, }s)");
+
+ deparse_withObj_ToJsonb(state, node);
+
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ }
+
+ /* TABLESPACE */
+ if (node->tablespacename)
+ {
+ appendStringInfoString(&fmtStr, " %{tablespace}s");
+ insert_jsonb_key(state, "tablespace");
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ new_jsonb_VA(state, 2,
+ "fmt", jbvString, "TABLESPACE %{tablespace}I",
+ "tablespace", jbvString, node->tablespacename);
+ pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ }
+
+ relation_close(relation, AccessShareLock);
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+ pfree(fmtStr.data);
+
+ /* Mark the end of ROOT object */
+ value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ return JsonbValueToJsonb(value);
+}
+
+/*
+ * Deparse a DropStmt (DROP TABLE).
+ *
+ * Given an object identity and the parse tree that created it, return
+ * jsonb string representing the drop command.
+ *
+ * Verbose syntax
+ * DROP %{objtype}s %{concurrently}s %{if_exists}s %{objidentity}s %{cascade}s
+ */
+char *
+deparse_drop_table(const char *objidentity, const char *objecttype,
+ Node *parsetree)
+{
+ DropStmt *node = (DropStmt *) parsetree;
+ StringInfoData fmtStr;
+ JsonbValue *jsonbval;
+ Jsonb *jsonb;
+ StringInfoData str;
+ JsonbParseState *state = NULL;
+
+ initStringInfo(&str);
+ initStringInfo(&fmtStr);
+
+ /* mark the begin of ROOT object and start adding elements to it. */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ /* Start constructing fmt string */
+ appendStringInfoString(&fmtStr, "DROP %{objtype}s");
+
+ /* OBJTYPE */
+ new_jsonb_VA(state, 1, "objtype", jbvString, objecttype);
+
+ /* CONCURRENTLY */
+ if (node->concurrent)
+ {
+ appendStringInfoString(&fmtStr, " %{concurrently}s");
+ new_jsonb_VA(state, 1,
+ "concurrently", jbvString, "CONCURRENTLY");
+ }
+
+ /* IF EXISTS */
+ if (node->missing_ok)
+ {
+ appendStringInfoString(&fmtStr, " %{if_exists}s");
+ new_jsonb_VA(state, 1, "if_exists", jbvString, "IF EXISTS");
+ }
+
+ /* IDENTITY */
+ appendStringInfoString(&fmtStr, " %{objidentity}s");
+ new_jsonb_VA(state, 1, "objidentity", jbvString, objidentity);
+
+ /* CASCADE */
+ if (node->behavior == DROP_CASCADE)
+ {
+ appendStringInfoString(&fmtStr, " %{cascade}s");
+ new_jsonb_VA(state, 1, "cascade", jbvString, "CASCADE");
+ }
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+ pfree(fmtStr.data);
+
+ jsonbval = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ jsonb = JsonbValueToJsonb(jsonbval);
+ return JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+}
+
+/*
+ * Deparse a CreateSeqStmt.
+ *
+ * Given a sequence OID and the parse tree that created it, return Jsonb
+ * representing the creation command.
+ *
+ * Note: We need to deparse the CREATE SEQUENCE command for the TABLE
+ * commands. For example, When creating a table, if we specify a column as a
+ * serial type, then we will create a sequence for that column and set that
+ * sequence OWNED BY the table. The serial column type information is not
+ * available during deparsing phase as that has already been converted to
+ * the column default value and sequences creation while parsing.
+ *
+ * Verbose syntax
+ * CREATE %{persistence}s SEQUENCE %{identity}D %{definition: }s
+ */
+static Jsonb *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+ Relation relation;
+ Form_pg_sequence seqform;
+ Sequence_values *seqvalues;
+ CreateSeqStmt *createSeqStmt = (CreateSeqStmt *) parsetree;
+ JsonbParseState *state = NULL;
+ JsonbValue *value;
+ StringInfoData fmtStr;
+ char *perstr;
+
+ /*
+ * Only support sequence for IDENTITY COLUMN output separately (via CREATE
+ * TABLE or ALTER TABLE). Otherwise, return empty here.
+ */
+ if (createSeqStmt->for_identity)
+ return NULL;
+
+ initStringInfo(&fmtStr);
+ relation = relation_open(objectId, AccessShareLock);
+
+ /* mark the start of ROOT object */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ appendStringInfoString(&fmtStr, "CREATE");
+
+ /* PERSISTENCE */
+ perstr = get_persistence_str(relation->rd_rel->relpersistence);
+ if (perstr)
+ {
+ appendStringInfoString(&fmtStr, " %{persistence}s");
+ new_jsonb_VA(state, 1,
+ "persistence", jbvString, perstr);
+ }
+
+ appendStringInfoString(&fmtStr, " SEQUENCE");
+
+ /* IF NOT EXISTS */
+ if (createSeqStmt->if_not_exists)
+ {
+ appendStringInfoString(&fmtStr, " %{if_not_exists}s");
+ new_jsonb_VA(state, 1,
+ "if_not_exists", jbvString, "IF NOT EXISTS");
+ }
+
+ /* IDENTITY */
+ appendStringInfoString(&fmtStr, " %{identity}D");
+ insert_identity_object(state, relation->rd_rel->relnamespace,
+ RelationGetRelationName(relation));
+
+ relation_close(relation, AccessShareLock);
+
+ seqvalues = get_sequence_values(objectId);
+ seqform = seqvalues->seqform;
+
+ /* sequence definition array object creation, push the key first */
+ appendStringInfoString(&fmtStr, " %{definition: }s");
+ insert_jsonb_key(state, "definition");
+
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+ /* Definition elements */
+ deparse_Seq_Cache_toJsonb(state, seqform);
+ deparse_Seq_Cycle_toJsonb(state, seqform);
+ deparse_Seq_IncrementBy_toJsonb(state, seqform);
+ deparse_Seq_Minvalue_toJsonb(state, seqform);
+ deparse_Seq_Maxvalue_toJsonb(state, seqform);
+ deparse_Seq_Startwith_toJsonb(state, seqform);
+ deparse_Seq_Restart_toJsonb(state, seqvalues->last_value);
+ deparse_Seq_As_toJsonb(state, seqform);
+ /* We purposefully do not emit OWNED BY here */
+
+ /* mark the end of sequence definition array */
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+ /* We have full fmt by now, so add jsonb element for that */
+ new_jsonb_VA(state, 1, "fmt", jbvString, fmtStr.data);
+
+ pfree(fmtStr.data);
+
+ /* mark the end of ROOT object */
+ value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ return JsonbValueToJsonb(value);
+}
+
+/*
+ * Deparse an AlterSeqStmt.
+ *
+ * Given a sequence OID and a parse tree that modified it, return Jsonb
+ * representing the alter command.
+ *
+ * Note: We need to deparse the ALTER SEQUENCE command for the TABLE commands.
+ * For example, When creating a table, if we specify a column as a serial
+ * type, then we will create a sequence for that column and set that sequence
+ * OWNED BY the table. The serial column type information is not available
+ * during deparsing phase as that has already been converted to the column
+ * default value and sequences creation while parsing.
+ *
+ * Verbose syntax
+ * ALTER SEQUENCE %{identity}D %{definition: }s
+ */
+static Jsonb *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+ Relation relation;
+ ListCell *cell;
+ Form_pg_sequence seqform;
+ Sequence_values *seqvalues;
+ AlterSeqStmt *alterSeqStmt = (AlterSeqStmt *) parsetree;
+ JsonbParseState *state = NULL;
+ JsonbValue *value;
+
+ /*
+ * Sequence for IDENTITY COLUMN output separately (via CREATE TABLE or
+ * ALTER TABLE); return empty here.
+ */
+ if (alterSeqStmt->for_identity)
+ return NULL;
+
+ relation = relation_open(objectId, AccessShareLock);
+
+ /* mark the start of ROOT object */
+ pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ new_jsonb_VA(state, 1,
+ "fmt", jbvString, "ALTER SEQUENCE %{identity}D %{definition: }s");
+
+ insert_identity_object(state, relation->rd_rel->relnamespace,
+ RelationGetRelationName(relation));
+ relation_close(relation, AccessShareLock);
+
+ seqvalues = get_sequence_values(objectId);
+ seqform = seqvalues->seqform;
+
+ /* sequence definition array object creation, push the key first */
+ insert_jsonb_key(state, "definition");
+
+ /* mark the start of sequence definition array */
+ pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
+
+ foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+ {
+ DefElem *elem = (DefElem *) lfirst(cell);
+
+ if (strcmp(elem->defname, "cache") == 0)
+ deparse_Seq_Cache_toJsonb(state, seqform);
+ else if (strcmp(elem->defname, "cycle") == 0)
+ deparse_Seq_Cycle_toJsonb(state, seqform);
+ else if (strcmp(elem->defname, "increment") == 0)
+ deparse_Seq_IncrementBy_toJsonb(state, seqform);
+ else if (strcmp(elem->defname, "minvalue") == 0)
+ deparse_Seq_Minvalue_toJsonb(state, seqform);
+ else if (strcmp(elem->defname, "maxvalue") == 0)
+ deparse_Seq_Maxvalue_toJsonb(state, seqform);
+ else if (strcmp(elem->defname, "start") == 0)
+ deparse_Seq_Startwith_toJsonb(state, seqform);
+ else if (strcmp(elem->defname, "restart") == 0)
+ deparse_Seq_Restart_toJsonb(state, seqvalues->last_value);
+ else if (strcmp(elem->defname, "owned_by") == 0)
+ deparse_Seq_OwnedBy_toJsonb(state, objectId);
+ else if (strcmp(elem->defname, "as") == 0)
+ deparse_Seq_As_toJsonb(state, seqform);
+ else
+ elog(ERROR, "invalid sequence option %s", elem->defname);
+ }
+
+ /* mark the end of sequence definition array */
+ pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+
+ /* mark the end of ROOT object */
+ value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+
+ return JsonbValueToJsonb(value);
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function should cover all cases handled in ProcessUtilitySlow.
+ */
+static Jsonb *
+deparse_simple_command(CollectedCommand *cmd)
+{
+ Oid objectId;
+ Node *parsetree;
+
+ Assert(cmd->type == SCT_Simple);
+
+ parsetree = cmd->parsetree;
+ objectId = cmd->d.simple.address.objectId;
+
+ if (cmd->in_extension && (nodeTag(parsetree) != T_CreateExtensionStmt))
+ return NULL;
+
+ /* This switch needs to handle everything that ProcessUtilitySlow does */
+ switch (nodeTag(parsetree))
+ {
+ case T_AlterSeqStmt:
+ return deparse_AlterSeqStmt(objectId, parsetree);
+
+ case T_CreateSeqStmt:
+ return deparse_CreateSeqStmt(objectId, parsetree);
+
+ case T_CreateStmt:
+ return deparse_CreateStmt(objectId, parsetree);
+
+ default:
+ elog(LOG, "unrecognized node type in deparse command: %d",
+ (int) nodeTag(parsetree));
+ }
+
+ return NULL;
+}
+
+/*
+ * Workhorse to deparse a CollectedCommand.
+ */
+char *
+deparse_utility_command(CollectedCommand *cmd)
+{
+ OverrideSearchPath *overridePath;
+ MemoryContext oldcxt;
+ MemoryContext tmpcxt;
+ char *command = NULL;
+ StringInfoData str;
+ Jsonb *jsonb;
+
+ /*
+ * Allocate everything done by the deparsing routines into a temp context,
+ * to avoid having to sprinkle them with memory handling code, but
+ * allocate the output StringInfo before switching.
+ */
+ initStringInfo(&str);
+ tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+ "deparse ctx",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+ /*
+ * Many routines underlying this one will invoke ruleutils.c functionality
+ * to obtain deparsed versions of expressions. In such results, we want
+ * all object names to be qualified, so that results are "portable" to
+ * environments with different search_path settings. Rather than
+ * injecting what would be repetitive calls to override search path all
+ * over the place, we do it centrally here.
+ */
+ overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+ overridePath->schemas = NIL;
+ overridePath->addCatalog = false;
+ overridePath->addTemp = true;
+ PushOverrideSearchPath(overridePath);
+
+ switch (cmd->type)
+ {
+ case SCT_Simple:
+ jsonb = deparse_simple_command(cmd);
+ break;
+ default:
+ elog(ERROR, "unexpected deparse node type %d", cmd->type);
+ }
+
+ PopOverrideSearchPath();
+
+ if (jsonb)
+ command = JsonbToCString(&str, &jsonb->root, JSONB_ESTIMATED_LEN);
+
+ /*
+ * Clean up. Note that since we created the StringInfo in the caller's
+ * context, the output string is not deleted here.
+ */
+ MemoryContextSwitchTo(oldcxt);
+ MemoryContextDelete(tmpcxt);
+
+ return command;
+}
+
+/*
+ * Given a CollectedCommand, return a JSON representation of it.
+ *
+ * The command is expanded fully so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+Datum
+ddl_deparse_to_json(PG_FUNCTION_ARGS)
+{
+ CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+ char *command;
+
+ command = deparse_utility_command(cmd);
+
+ if (command)
+ PG_RETURN_TEXT_P(cstring_to_text(command));
+
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/commands/ddljson.c b/src/backend/commands/ddljson.c
new file mode 100644
index 0000000000..d5c968b7c1
--- /dev/null
+++ b/src/backend/commands/ddljson.c
@@ -0,0 +1,759 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddljson.c
+ * JSON code related to DDL command deparsing
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *
+ * Each JSONB object is supposed to have a "fmt" which will tell expansion
+ * routines how JSONB can be expanded to construct ddl command. One example
+ * snippet from JSONB object for 'ALTER TABLE sales ADD col1 int':
+ *
+ * { *1-level*
+ * "fmt": "ALTER %{objtype}s %{only}s %{identity}D %{subcmds:, }s",
+ * "only": "",
+ * "objtype": "TABLE",
+ * "identity": {"objname": "sales", "schemaname": "public"}
+ * "subcmds": [
+ * { *2-level*
+ * "fmt": "ADD %{objtype}s %{if_not_exists}s %{definition}s",
+ * "type": "add column",
+ * "objtype": "COLUMN",
+ * "definition": {}
+ * ...
+ * }
+ * ...
+ * }
+ *
+ * From above, we can see different key-value pairs.
+ * level-1 represents ROOT object with 'fmt', 'only', 'objtype','identity',
+ * 'subcmds' as the keys with the values appended after ":" with each key.
+ * Value can be string, bool, numeric, array or any nested object. As an
+ * example, "objtype" has string value while "subcmds" has nested-object
+ * as its value which can further have multiple key-value pairs.
+ *
+ * The value of "fmt" tells us how the expansion will be carried on. The
+ * value of "fmt" may contain zero or more %-escapes, which consist of key
+ * name enclosed in { }, followed by a conversion specifier which tells us
+ * how the value for that particular key should be expanded.
+ * Possible conversion specifiers are:
+ * % expand to a literal %
+ * I expand as a single, non-qualified identifier
+ * D expand as a possibly-qualified identifier
+ * T expand as a type name
+ * L expand as a string literal (quote using single quotes)
+ * s expand as a simple string (no quoting)
+ * n expand as a simple number (no quoting)
+ *
+ * In order to build a DDL command, it will first extract "fmt" node in
+ * jsonb string and will read each key name enclosed in { } in fmt-string
+ * and will replace it with its value. For each name mentioned in { } in
+ * fmt string, there must be a key-value pair, in absence of which, the
+ * expansion will error out. While doing this expansion, it will consider
+ * the conversion-specifier maintained with each key in fmt string to figure
+ * out how value should actually be represented. This is how DDL command can
+ * be constructed back from the jsonb-string.
+ *
+ * IDENTIFICATION
+ * src/backend/commands/ddljson.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "tcop/ddldeparse.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+
+#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \
+ do { \
+ if (++(ptr) >= (end_ptr)) \
+ ereport(ERROR, \
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+ errmsg("unterminated format specifier")); \
+ } while (0)
+
+/*
+ * Conversion specifier which determines how to expand the JSON element
+ * into a string.
+ */
+typedef enum
+{
+ SpecDottedName,
+ SpecIdentifier,
+ SpecNumber,
+ SpecString,
+ SpecStringLiteral,
+ SpecTypeName
+} convSpecifier;
+
+/*
+ * A ternary value that represents a boolean type JsonbValue.
+ */
+typedef enum
+{
+ tv_absent,
+ tv_true,
+ tv_false
+} json_trivalue;
+
+static bool expand_one_jsonb_element(StringInfo buf, char *param,
+ JsonbValue *jsonval, convSpecifier specifier,
+ const char *fmt);
+static void expand_jsonb_array(StringInfo buf, char *param,
+ JsonbValue *jsonarr, char *arraysep,
+ convSpecifier specifier, const char *fmt);
+static void fmtstr_error_callback(void *arg);
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvBool, an error is raised. If it doesn't
+ * exist, tv_absent is returned; otherwise return the actual json_trivalue.
+ */
+static json_trivalue
+find_bool_in_jsonbcontainer(JsonbContainer *container, char *keyname)
+{
+ JsonbValue key;
+ JsonbValue *value;
+ json_trivalue result;
+
+ key.type = jbvString;
+ key.val.string.val = keyname;
+ key.val.string.len = strlen(keyname);
+ value = findJsonbValueFromContainer(container,
+ JB_FOBJECT, &key);
+ if (value == NULL)
+ return tv_absent;
+ if (value->type != jbvBool)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("element \"%s\" is not of type boolean", keyname));
+ result = value->val.boolean ? tv_true : tv_false;
+ pfree(value);
+
+ return result;
+}
+
+/*
+ * Given a JsonbContainer, find the JsonbValue with the given key name in it.
+ * If it's of a type other than jbvString, an error is raised. If it doesn't
+ * exist, an error is raised unless missing_ok; otherwise return NULL.
+ *
+ * If it exists and is a string, a freshly palloc'ed copy is returned.
+ *
+ * If *length is not NULL, it is set to the length of the string.
+ */
+static char *
+find_string_in_jsonbcontainer(JsonbContainer *container, char *keyname,
+ bool missing_ok, int *length)
+{
+ JsonbValue key;
+ JsonbValue *value;
+ char *str;
+
+ /* XXX verify that this is an object, not an array */
+
+ key.type = jbvString;
+ key.val.string.val = keyname;
+ key.val.string.len = strlen(keyname);
+ value = findJsonbValueFromContainer(container,
+ JB_FOBJECT, &key);
+ if (value == NULL)
+ {
+ if (missing_ok)
+ return NULL;
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("missing element \"%s\" in JSON object", keyname));
+ }
+
+ if (value->type != jbvString)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("element \"%s\" is not of type string", keyname));
+
+ str = pnstrdup(value->val.string.val, value->val.string.len);
+ if (length)
+ *length = value->val.string.len;
+ pfree(value);
+ return str;
+}
+
+/*
+ * Recursive helper for deparse_ddl_json_to_string.
+ *
+ * Find the "fmt" element in the given container, and expand it into the
+ * provided StringInfo.
+ */
+static void
+expand_fmt_recursive(StringInfo buf, JsonbContainer *container)
+{
+ JsonbValue key;
+ JsonbValue *value;
+ const char *cp;
+ const char *start_ptr;
+ const char *end_ptr;
+ int len;
+
+ start_ptr = find_string_in_jsonbcontainer(container, "fmt", false, &len);
+ end_ptr = start_ptr + len;
+
+ for (cp = start_ptr; cp < end_ptr; cp++)
+ {
+ convSpecifier specifier;
+ bool is_array = false;
+ char *param = NULL;
+ char *arraysep = NULL;
+
+ if (*cp != '%')
+ {
+ appendStringInfoCharMacro(buf, *cp);
+ continue;
+ }
+
+ ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+ /* Easy case: %% outputs a single % */
+ if (*cp == '%')
+ {
+ appendStringInfoCharMacro(buf, *cp);
+ continue;
+ }
+
+ /*
+ * Scan the mandatory element name. Allow for an array separator
+ * (which may be the empty string) to be specified after a colon.
+ */
+ if (*cp == '{')
+ {
+ StringInfoData parbuf;
+ StringInfoData arraysepbuf;
+ StringInfo appendTo;
+
+ initStringInfo(&parbuf);
+ appendTo = &parbuf;
+
+ ADVANCE_PARSE_POINTER(cp, end_ptr);
+ while (cp < end_ptr)
+ {
+ if (*cp == ':')
+ {
+ /*
+ * Found array separator delimiter; element name is now
+ * complete, start filling the separator.
+ */
+ initStringInfo(&arraysepbuf);
+ appendTo = &arraysepbuf;
+ is_array = true;
+ ADVANCE_PARSE_POINTER(cp, end_ptr);
+ continue;
+ }
+
+ if (*cp == '}')
+ {
+ ADVANCE_PARSE_POINTER(cp, end_ptr);
+ break;
+ }
+ appendStringInfoCharMacro(appendTo, *cp);
+ ADVANCE_PARSE_POINTER(cp, end_ptr);
+ }
+ param = parbuf.data;
+ if (is_array)
+ arraysep = arraysepbuf.data;
+ }
+ if (param == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("missing conversion name in conversion specifier"));
+
+ switch (*cp)
+ {
+ case 'I':
+ specifier = SpecIdentifier;
+ break;
+ case 'D':
+ specifier = SpecDottedName;
+ break;
+ case 's':
+ specifier = SpecString;
+ break;
+ case 'L':
+ specifier = SpecStringLiteral;
+ break;
+ case 'T':
+ specifier = SpecTypeName;
+ break;
+ case 'n':
+ specifier = SpecNumber;
+ break;
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid conversion specifier \"%c\"", *cp));
+ }
+
+ /*
+ * Obtain the element to be expanded.
+ */
+ key.type = jbvString;
+ key.val.string.val = param;
+ key.val.string.len = strlen(param);
+
+ value = findJsonbValueFromContainer(container, JB_FOBJECT, &key);
+ Assert(value != NULL);
+
+ /*
+ * Expand the data (possibly an array) into the output StringInfo.
+ */
+ if (is_array)
+ expand_jsonb_array(buf, param, value, arraysep, specifier, start_ptr);
+ else
+ expand_one_jsonb_element(buf, param, value, specifier, start_ptr);
+
+ pfree(value);
+ }
+}
+
+/*
+ * Expand a json value as a quoted identifier. The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, JsonbValue *jsonval)
+{
+ char *str;
+
+ Assert(jsonval->type == jbvString);
+
+ str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+ appendStringInfoString(buf, quote_identifier(str));
+ pfree(str);
+}
+
+/*
+ * Expand a json value as a dot-separated-name. The value must be of type
+ * binary and may contain elements "schemaname" (optional), "objname"
+ * (mandatory), "attrname" (optional). Double quotes are added to each element
+ * as necessary, and dot separators where needed.
+ *
+ * One day we might need a "catalog" element as well, but no current use case
+ * needs that.
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, JsonbValue *jsonval)
+{
+ char *str;
+ JsonbContainer *data = jsonval->val.binary.data;
+
+ Assert(jsonval->type == jbvBinary);
+
+ str = find_string_in_jsonbcontainer(data, "schemaname", true, NULL);
+ if (str)
+ {
+ appendStringInfo(buf, "%s.", quote_identifier(str));
+ pfree(str);
+ }
+
+ str = find_string_in_jsonbcontainer(data, "objname", false, NULL);
+ appendStringInfo(buf, "%s", quote_identifier(str));
+ pfree(str);
+
+ str = find_string_in_jsonbcontainer(data, "attrname", true, NULL);
+ if (str)
+ {
+ appendStringInfo(buf, ".%s", quote_identifier(str));
+ pfree(str);
+ }
+}
+
+/*
+ * Expand a JSON value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, JsonbValue *jsonval)
+{
+ char *schema = NULL;
+ char *typename = NULL;
+ char *typmodstr = NULL;
+ json_trivalue is_array;
+ char *array_decor;
+ JsonbContainer *data = jsonval->val.binary.data;
+
+ /*
+ * We omit schema-qualifying the output name if the schema element is
+ * either the empty string or NULL; the difference between those two cases
+ * is that in the latter we quote the type name, in the former we don't.
+ * This allows for types with special typmod needs, such as interval and
+ * timestamp (see format_type_detailed), while at the same time allowing
+ * for the schema name to be omitted from type names that require quotes
+ * but are to be obtained from a user schema.
+ */
+
+ schema = find_string_in_jsonbcontainer(data, "schemaname", true, NULL);
+ typename = find_string_in_jsonbcontainer(data, "typename", false, NULL);
+ typmodstr = find_string_in_jsonbcontainer(data, "typmod", true, NULL);
+ is_array = find_bool_in_jsonbcontainer(data, "typarray");
+ switch (is_array)
+ {
+ case tv_true:
+ array_decor = "[]";
+ break;
+
+ case tv_false:
+ array_decor = "";
+ break;
+
+ case tv_absent:
+ default:
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("missing typarray element"));
+ }
+
+ if (schema == NULL)
+ appendStringInfo(buf, "%s", quote_identifier(typename));
+ else if (schema[0] == '\0')
+ appendStringInfo(buf, "%s", typename); /* Special typmod needs */
+ else
+ appendStringInfo(buf, "%s.%s", quote_identifier(schema),
+ quote_identifier(typename));
+
+ appendStringInfo(buf, "%s%s", typmodstr ? typmodstr : "", array_decor);
+
+ if (schema)
+ pfree(schema);
+ if (typename)
+ pfree(typename);
+ if (typmodstr)
+ pfree(typmodstr);
+}
+
+/*
+ * Expand a JSON value as a string. The value must be of type string or of
+ * type Binary. In the latter case, it must contain a "fmt" element which will
+ * be recursively expanded; also, if the object contains an element "present"
+ * and it is set to false, the expansion is the empty string.
+ *
+ * Returns false if no actual expansion was made due to the "present" flag
+ * being set to "false".
+ *
+ * The caller is responsible to check jsonval is of type jbvString or jbvBinary.
+ */
+static bool
+expand_jsonval_string(StringInfo buf, JsonbValue *jsonval)
+{
+ bool expanded = false;
+
+ Assert((jsonval->type == jbvString) || (jsonval->type == jbvBinary));
+
+ if (jsonval->type == jbvString)
+ {
+ appendBinaryStringInfo(buf, jsonval->val.string.val,
+ jsonval->val.string.len);
+ expanded = true;
+ }
+ else if (jsonval->type == jbvBinary)
+ {
+ json_trivalue present;
+
+ present = find_bool_in_jsonbcontainer(jsonval->val.binary.data,
+ "present");
+
+ /*
+ * If "present" is set to false, this element expands to empty;
+ * otherwise (either true or absent), expand "fmt".
+ */
+ if (present != tv_false)
+ {
+ expand_fmt_recursive(buf, jsonval->val.binary.data);
+ expanded = true;
+ }
+ }
+
+ return expanded;
+}
+
+/*
+ * Expand a JSON value as a string literal.
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, JsonbValue *jsonval)
+{
+ char *str;
+ StringInfoData dqdelim;
+ static const char dqsuffixes[] = "_XYZZYX_";
+ int dqnextchar = 0;
+
+ Assert(jsonval->type == jbvString);
+
+ str = pnstrdup(jsonval->val.string.val, jsonval->val.string.len);
+
+ /* Easy case: if there are no ' and no \, just use a single quote */
+ if (strpbrk(str, "\'\\") == NULL)
+ {
+ appendStringInfo(buf, "'%s'", str);
+ pfree(str);
+ return;
+ }
+
+ /* Otherwise need to find a useful dollar-quote delimiter */
+ initStringInfo(&dqdelim);
+ appendStringInfoString(&dqdelim, "$");
+ while (strstr(str, dqdelim.data) != NULL)
+ {
+ appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+ dqnextchar = dqnextchar % (sizeof(dqsuffixes) - 1);
+ }
+ /* Add trailing $ */
+ appendStringInfoChar(&dqdelim, '$');
+
+ /* And finally produce the quoted literal into the output StringInfo */
+ appendStringInfo(buf, "%s%s%s", dqdelim.data, str, dqdelim.data);
+ pfree(dqdelim.data);
+ pfree(str);
+}
+
+/*
+ * Expand a JSON value as an integer quantity.
+ */
+static void
+expand_jsonval_number(StringInfo buf, JsonbValue *jsonval)
+{
+ char *strdatum;
+
+ Assert(jsonval->type == jbvNumeric);
+
+ strdatum = DatumGetCString(DirectFunctionCall1(numeric_out,
+ NumericGetDatum(jsonval->val.numeric)));
+ appendStringInfoString(buf, strdatum);
+ pfree(strdatum);
+}
+
+/*
+ * Expand one JSON element into the output StringInfo according to the
+ * conversion specifier. The element type is validated, and an error is raised
+ * if it doesn't match what we expect for the conversion specifier.
+ *
+ * Returns true, except for the formatted string case if no actual expansion
+ * was made (due to the "present" flag being set to "false").
+ */
+static bool
+expand_one_jsonb_element(StringInfo buf, char *param, JsonbValue *jsonval,
+ convSpecifier specifier, const char *fmt)
+{
+ bool string_expanded = true;
+ ErrorContextCallback sqlerrcontext;
+
+ /* If we were given a format string, setup an ereport() context callback */
+ if (fmt)
+ {
+ sqlerrcontext.callback = fmtstr_error_callback;
+ sqlerrcontext.arg = (void *) fmt;
+ sqlerrcontext.previous = error_context_stack;
+ error_context_stack = &sqlerrcontext;
+ }
+
+ if (!jsonval)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("element \"%s\" not found", param));
+
+ switch (specifier)
+ {
+ case SpecIdentifier:
+ if (jsonval->type != jbvString)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("expected JSON string for %%I element \"%s\", got %d",
+ param, jsonval->type));
+ expand_jsonval_identifier(buf, jsonval);
+ break;
+
+ case SpecDottedName:
+ if (jsonval->type != jbvBinary)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("expected JSON struct for %%D element \"%s\", got %d",
+ param, jsonval->type));
+ expand_jsonval_dottedname(buf, jsonval);
+ break;
+
+ case SpecString:
+ if (jsonval->type != jbvString &&
+ jsonval->type != jbvBinary)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("expected JSON string or struct for %%s element \"%s\", got %d",
+ param, jsonval->type));
+ string_expanded = expand_jsonval_string(buf, jsonval);
+ break;
+
+ case SpecStringLiteral:
+ if (jsonval->type != jbvString)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("expected JSON string for %%L element \"%s\", got %d",
+ param, jsonval->type));
+ expand_jsonval_strlit(buf, jsonval);
+ break;
+
+ case SpecTypeName:
+ if (jsonval->type != jbvBinary)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("expected JSON struct for %%T element \"%s\", got %d",
+ param, jsonval->type));
+ expand_jsonval_typename(buf, jsonval);
+ break;
+
+ case SpecNumber:
+ if (jsonval->type != jbvNumeric)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("expected JSON numeric for %%n element \"%s\", got %d",
+ param, jsonval->type));
+ expand_jsonval_number(buf, jsonval);
+ break;
+ }
+
+ if (fmt)
+ error_context_stack = sqlerrcontext.previous;
+
+ return string_expanded;
+}
+
+/*
+ * Iterate on the elements of a JSON array, expanding each one into the output
+ * StringInfo per the given conversion specifier, separated by the given
+ * separator.
+ */
+static void
+expand_jsonb_array(StringInfo buf, char *param,
+ JsonbValue *jsonarr, char *arraysep, convSpecifier specifier,
+ const char *fmt)
+{
+ ErrorContextCallback sqlerrcontext;
+ JsonbContainer *container;
+ JsonbIterator *it;
+ JsonbValue v;
+ int type;
+ bool first = true;
+ StringInfoData arrayelem;
+
+ /* If we were given a format string, setup an ereport() context callback */
+ if (fmt)
+ {
+ sqlerrcontext.callback = fmtstr_error_callback;
+ sqlerrcontext.arg = (void *) fmt;
+ sqlerrcontext.previous = error_context_stack;
+ error_context_stack = &sqlerrcontext;
+ }
+
+ if (!jsonarr)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("element \"%s\" not found", param));
+
+ if (jsonarr->type != jbvBinary)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("element \"%s\" is not a JSON array", param));
+
+ container = jsonarr->val.binary.data;
+ if (!JsonContainerIsArray(container))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("element \"%s\" is not a JSON array", param));
+
+ initStringInfo(&arrayelem);
+
+ it = JsonbIteratorInit(container);
+ while ((type = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (type == WJB_ELEM)
+ {
+ resetStringInfo(&arrayelem);
+
+ if (expand_one_jsonb_element(&arrayelem, param, &v, specifier, NULL))
+ {
+ if (!first)
+ appendStringInfoString(buf, arraysep);
+
+ appendBinaryStringInfo(buf, arrayelem.data, arrayelem.len);
+ first = false;
+ }
+ }
+ }
+
+ if (fmt)
+ error_context_stack = sqlerrcontext.previous;
+}
+
+/*
+ * Workhorse for ddl_deparse_expand_command.
+ */
+char *
+deparse_ddl_json_to_string(char *json_str)
+{
+ Datum d;
+ Jsonb *jsonb;
+ StringInfo buf = (StringInfo) palloc0(sizeof(StringInfoData));
+
+ initStringInfo(buf);
+
+ d = DirectFunctionCall1(jsonb_in, PointerGetDatum(json_str));
+ jsonb = (Jsonb *) DatumGetPointer(d);
+
+ expand_fmt_recursive(buf, &jsonb->root);
+
+ return buf->data;
+}
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier. Possible conversion specifiers are:
+ *
+ * % expand to a literal %
+ * I expand as a single, non-qualified identifier
+ * D expand as a possibly-qualified identifier
+ * T expand as a type name
+ * L expand as a string literal (quote using single quotes)
+ * s expand as a simple string (no quoting)
+ * n expand as a simple number (no quoting)
+ *
+ * The element name may have an optional separator specification preceded
+ * by a colon. Its presence indicates that the element is expected to be
+ * an array; the specified separator is used to join the array elements.
+ *
+ * The actual conversion of single JSON element into string according to
+ * above conversion specifiers takes place in expand_one_jsonb_element()
+ *------
+ */
+Datum
+ddl_deparse_expand_command(PG_FUNCTION_ARGS)
+{
+ text *json = PG_GETARG_TEXT_P(0);
+ char *json_str;
+
+ json_str = text_to_cstring(json);
+
+ PG_RETURN_TEXT_P(cstring_to_text(deparse_ddl_json_to_string(json_str)));
+}
+
+/*
+ * Error context callback for JSON format string expansion.
+ *
+ * XXX: indicate which element we're expanding, if applicable.
+ */
+static void
+fmtstr_error_callback(void *arg)
+{
+ errcontext("while expanding format string \"%s\"", (char *) arg);
+}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index d4b00d1a82..4d48e490ed 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -37,6 +37,7 @@
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "pgstat.h"
+#include "tcop/ddldeparse.h"
#include "tcop/deparse_utility.h"
#include "tcop/utility.h"
#include "utils/acl.h"
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 42cced9ebe..9539e53bac 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -17,6 +17,8 @@ backend_sources += files(
'copyto.c',
'createas.c',
'dbcommands.c',
+ 'ddldeparse.c',
+ 'ddljson.c',
'define.c',
'discard.c',
'dropcmds.c',
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ef01449678..4bb731d5ff 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1707,6 +1707,49 @@ process_owned_by(Relation seqrel, List *owned_by, bool for_identity)
relation_close(tablerel, NoLock);
}
+/*
+ * Return sequence parameters, detailed
+ */
+Sequence_values *
+get_sequence_values(Oid sequenceId)
+{
+ Buffer buf;
+ SeqTable elm;
+ Relation seqrel;
+ HeapTuple seqtuple;
+ HeapTupleData seqtupledata;
+ Form_pg_sequence seqform;
+ Form_pg_sequence_data seq;
+ Sequence_values *seqvalues;
+
+ seqtuple = SearchSysCache1(SEQRELID, sequenceId);
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u", sequenceId);
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+
+ ReleaseSysCache(seqtuple);
+
+ /* Open and lock sequence */
+ init_sequence(sequenceId, &elm, &seqrel);
+
+ if (pg_class_aclcheck(sequenceId, GetUserId(),
+ ACL_SELECT | ACL_USAGE) != ACLCHECK_OK)
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for sequence %s",
+ RelationGetRelationName(seqrel)));
+
+ seq = read_seq_tuple(seqrel, &buf, &seqtupledata);
+
+ seqvalues = (Sequence_values *) palloc(sizeof(Sequence_values));
+ seqvalues->last_value = seq->last_value;
+ seqvalues->seqform = seqform;
+
+ UnlockReleaseBuffer(buf);
+ relation_close(seqrel, NoLock);
+
+ return seqvalues;
+}
/*
* Return sequence parameters in a list of the form created by the parser.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4d49d70c33..c15e943f1d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -600,7 +600,6 @@ static ObjectAddress ATExecSetCompression(Relation rel,
const char *column, Node *newValue, LOCKMODE lockmode);
static void index_copy_data(Relation rel, RelFileLocator newrlocator);
-static const char *storage_name(char c);
static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
@@ -2266,7 +2265,7 @@ truncate_check_activity(Relation rel)
* storage_name
* returns the name corresponding to a typstorage/attstorage enum value
*/
-static const char *
+char *
storage_name(char c)
{
switch (c)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index d67580fc77..753456ecbc 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1393,6 +1393,7 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
atcmd->cmds = atsubcmds;
atcmd->objtype = OBJECT_TABLE;
atcmd->missing_ok = false;
+ atcmd->table_like = true;
result = lcons(atcmd, result);
}
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 12402a0637..4318129558 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -27,8 +27,6 @@
#include "utils/numeric.h"
#include "utils/syscache.h"
-static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
-
/*
* SQL function: format_type(type_oid, typemod)
@@ -329,6 +327,110 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
return buf;
}
+/*
+ * Similar to format_type_extended, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID. For certain SQL-standard types which have weird
+ * typmod rules, we return InvalidOid; the caller is expected to not schema-
+ * qualify the name nor add quotes to the type name in this case.
+ *
+ * - typname is set to the type name, without quotes
+ *
+ * - typemodstr is set to the typemod, if any, as a string with parentheses
+ *
+ * - typarray indicates whether []s must be added
+ *
+ * We don't try to decode type names to their standard-mandated names, except
+ * in the cases of types with unusual typmod rules.
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+ Oid *nspid, char **typname, char **typemodstr,
+ bool *typearray)
+{
+ HeapTuple tuple;
+ Form_pg_type typeform;
+ Oid array_base_type;
+
+ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for type with OID %u", type_oid);
+
+ typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+ /*
+ * We switch our attention to the array element type for certain cases.
+ * Check if it's a "true" array type. Pseudo-array types such as "name"
+ * shouldn't get deconstructed. Also check the toast property, and don't
+ * deconstruct "plain storage" array types --- this is because we don't
+ * want to show oidvector as oid[].
+ */
+ array_base_type = typeform->typelem;
+
+ *typearray = (IsTrueArrayType(typeform) && typeform->typstorage != TYPSTORAGE_PLAIN);
+
+ if (*typearray)
+ {
+ ReleaseSysCache(tuple);
+ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for type with OID %u", type_oid);
+
+ typeform = (Form_pg_type) GETSTRUCT(tuple);
+ type_oid = array_base_type;
+ }
+
+ /*
+ * Special-case crock for types with strange typmod rules where we put
+ * typemod at the middle of name (e.g. TIME(6) with time zone). We cannot
+ * schema-qualify nor add quotes to the type name in these cases.
+ */
+ *nspid = InvalidOid;
+
+ switch (type_oid)
+ {
+ case TIMEOID:
+ *typname = pstrdup("TIME");
+ break;
+ case TIMESTAMPOID:
+ *typname = pstrdup("TIMESTAMP");
+ break;
+ case TIMESTAMPTZOID:
+ if (typemod < 0)
+ *typname = pstrdup("TIMESTAMP WITH TIME ZONE");
+ else
+ /* otherwise, WITH TZ is added by typmod. */
+ *typname = pstrdup("TIMESTAMP");
+ break;
+ case INTERVALOID:
+ *typname = pstrdup("INTERVAL");
+ break;
+ case TIMETZOID:
+ if (typemod < 0)
+ *typname = pstrdup("TIME WITH TIME ZONE");
+ else
+ /* otherwise, WITH TZ is added by typmod. */
+ *typname = pstrdup("TIME");
+ break;
+ default:
+
+ /*
+ * No additional processing is required for other types, so get
+ * the type name and schema directly from the catalog.
+ */
+ *nspid = typeform->typnamespace;
+ *typname = pstrdup(NameStr(typeform->typname));
+ }
+
+ if (typemod >= 0)
+ *typemodstr = printTypmod("", typemod, typeform->typmodout);
+ else
+ *typemodstr = pstrdup("");
+
+ ReleaseSysCache(tuple);
+}
+
/*
* This version is for use within the backend in error messages, etc.
* One difference is that it will fail for an invalid type.
@@ -363,7 +465,7 @@ format_type_with_typemod(Oid type_oid, int32 typemod)
/*
* Add typmod decoration to the basic type name
*/
-static char *
+char *
printTypmod(const char *typname, int32 typmod, Oid typmodout)
{
char *res;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d3a973d86b..b8729d8e3d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -51,7 +51,6 @@
#include "optimizer/optimizer.h"
#include "parser/parse_agg.h"
#include "parser/parse_func.h"
-#include "parser/parse_node.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parser.h"
@@ -501,22 +500,15 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
deparse_context *context);
static void get_tablesample_def(TableSampleClause *tablesample,
deparse_context *context);
-static void get_opclass_name(Oid opclass, Oid actual_datatype,
- StringInfo buf);
static Node *processIndirection(Node *node, deparse_context *context);
static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context);
static char *get_relation_name(Oid relid);
static char *generate_relation_name(Oid relid, List *namespaces);
static char *generate_qualified_relation_name(Oid relid);
-static char *generate_function_name(Oid funcid, int nargs,
- List *argnames, Oid *argtypes,
- bool has_variadic, bool *use_variadic_p,
- ParseExprKind special_exprkind);
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static void add_cast_to(StringInfo buf, Oid typid);
static char *generate_qualified_type_name(Oid typid);
static text *string_to_text(char *str);
-static char *flatten_reloptions(Oid relid);
static void get_reloptions(StringInfo buf, Datum reloptions);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -1901,6 +1893,14 @@ pg_get_partkeydef_columns(Oid relid, bool pretty)
return pg_get_partkeydef_worker(relid, prettyFlags, true, false);
}
+/* Internal version that reports the full partition key definition */
+char *
+pg_get_partkeydef_string(Oid relid)
+{
+ return pg_get_partkeydef_worker(relid, GET_PRETTY_FLAGS(false), false,
+ false);
+}
+
/*
* Internal workhorse to decompile a partition key definition.
*/
@@ -2148,6 +2148,16 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(res));
}
+/*
+ * Internal version that returns the definition of a CONSTRAINT command
+ */
+char *
+pg_get_constraintdef_string(Oid constraintId)
+{
+ return pg_get_constraintdef_worker(constraintId, false,
+ GET_PRETTY_FLAGS(false), false);
+}
+
/*
* Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command
*/
@@ -11758,7 +11768,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
* actual_datatype. (If you don't want this behavior, just pass
* InvalidOid for actual_datatype.)
*/
-static void
+void
get_opclass_name(Oid opclass, Oid actual_datatype,
StringInfo buf)
{
@@ -12152,7 +12162,7 @@ generate_qualified_relation_name(Oid relid)
*
* The result includes all necessary quoting and schema-prefixing.
*/
-static char *
+char *
generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
bool has_variadic, bool *use_variadic_p,
ParseExprKind special_exprkind)
@@ -12538,7 +12548,7 @@ get_reloptions(StringInfo buf, Datum reloptions)
/*
* Generate a C string representing a relation's reloptions, or NULL if none.
*/
-static char *
+char *
flatten_reloptions(Oid relid)
{
char *result = NULL;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..76ff23b779 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12043,4 +12043,11 @@
proname => 'any_value_transfn', prorettype => 'anyelement',
proargtypes => 'anyelement anyelement', prosrc => 'any_value_transfn' },
+{ oid => '4642', descr => 'deparse the DDL command into a JSON format string',
+ proname => 'ddl_deparse_to_json', prorettype => 'text',
+ proargtypes => 'pg_ddl_command', prosrc => 'ddl_deparse_to_json' },
+{ oid => '4643', descr => 'expand JSON format DDL to a plain text DDL command',
+ proname => 'ddl_deparse_expand_command', prorettype => 'text',
+ proargtypes => 'text', prosrc => 'ddl_deparse_expand_command' },
+
]
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 7db7b3da7b..c0a39596ac 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -15,6 +15,7 @@
#include "access/xlogreader.h"
#include "catalog/objectaddress.h"
+#include "catalog/pg_sequence.h"
#include "fmgr.h"
#include "lib/stringinfo.h"
#include "nodes/parsenodes.h"
@@ -51,9 +52,17 @@ typedef struct xl_seq_rec
/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
} xl_seq_rec;
+/* Information needed to define a sequence. */
+typedef struct Sequence_values
+{
+ Form_pg_sequence seqform;
+ int64 last_value;
+} Sequence_values;
+
extern int64 nextval_internal(Oid relid, bool check_permissions);
extern Datum nextval(PG_FUNCTION_ARGS);
extern List *sequence_options(Oid relid);
+extern Sequence_values *get_sequence_values(Oid sequenceId);
extern ObjectAddress DefineSequence(ParseState *pstate, CreateSeqStmt *seq);
extern ObjectAddress AlterSequence(ParseState *pstate, AlterSeqStmt *stmt);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 17b9404937..5a082e57ea 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -106,4 +106,6 @@ extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
List *partConstraint);
+extern char *storage_name(char c);
+
#endif /* TABLECMDS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b3bec90e52..b8ab8dae6c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2167,6 +2167,7 @@ typedef struct AlterTableStmt
List *cmds; /* list of subcommands */
ObjectType objtype; /* type of object */
bool missing_ok; /* skip error if table missing */
+ bool table_like; /* internally generated for TableLikeClause */
} AlterTableStmt;
typedef enum AlterTableType
diff --git a/src/include/tcop/ddldeparse.h b/src/include/tcop/ddldeparse.h
new file mode 100644
index 0000000000..8b369fd8c5
--- /dev/null
+++ b/src/include/tcop/ddldeparse.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddldeparse.h
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/ddldeparse.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DDL_DEPARSE_H
+#define DDL_DEPARSE_H
+
+#include "tcop/deparse_utility.h"
+
+extern char *deparse_utility_command(CollectedCommand *cmd);
+extern char *deparse_ddl_json_to_string(char *jsonb);
+extern char *deparse_drop_table(const char *objidentity, const char *objecttype,
+ Node *parsetree);
+
+#endif /* DDL_DEPARSE_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2f8b46d6da..cfda299dee 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -124,9 +124,14 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
#define FORMAT_TYPE_INVALID_AS_NULL 0x08 /* NULL if undefined */
extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
+extern void format_type_detailed(Oid type_oid, int32 typemod,
+ Oid *nspid, char **typname,
+ char **typemodstr, bool *typearray);
+
extern char *format_type_be(Oid type_oid);
extern char *format_type_be_qualified(Oid type_oid);
extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
+extern char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
extern int32 type_maximum_size(Oid type_oid, int32 typemod);
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index b006d9d475..fd1d85fc8d 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -16,6 +16,7 @@
#include "nodes/nodes.h"
#include "nodes/parsenodes.h"
#include "nodes/pg_list.h"
+#include "parser/parse_node.h"
struct Plan; /* avoid including plannodes.h here */
struct PlannedStmt;
@@ -31,9 +32,11 @@ extern char *pg_get_indexdef_columns_extended(Oid indexrelid,
extern char *pg_get_querydef(Query *query, bool pretty);
extern char *pg_get_partkeydef_columns(Oid relid, bool pretty);
+extern char *pg_get_partkeydef_string(Oid relid);
extern char *pg_get_partconstrdef_string(Oid partitionId, char *aliasname);
extern char *pg_get_constraintdef_command(Oid constraintId);
+extern char *pg_get_constraintdef_string(Oid constraintId);
extern char *deparse_expression(Node *expr, List *dpcontext,
bool forceprefix, bool showimplicit);
extern List *deparse_context_for(const char *aliasname, Oid relid);
@@ -45,7 +48,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 260854747b..7c8ae7fe7b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3222,6 +3222,7 @@ config_var_value
contain_aggs_of_level_context
contain_placeholder_references_context
convert_testexpr_context
+convSpecifier
copy_data_dest_cb
copy_data_source_cb
core_YYSTYPE
@@ -3425,6 +3426,7 @@ json_manifest_perwalrange_callback
json_ofield_action
json_scalar_action
json_struct_action
+json_trivalue
keyEntryData
key_t
lclContext
--
2.34.1
[application/octet-stream] 0005-Apply-the-DDL-change-as-that-same-user-th-2023_06_22.patch (59.2K, 5-0005-Apply-the-DDL-change-as-that-same-user-th-2023_06_22.patch)
download | inline diff:
From 300d0d29e74075f7360c499b50ba6b63f9918ae6 Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Wed, 21 Jun 2023 10:34:12 +0530
Subject: [PATCH 5/5] Apply the DDL change as that same user that executed the
DDL on publisher
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/ddldeparse.c | 45 +++++-
src/backend/commands/ddljson.c | 25 ++-
src/backend/commands/event_trigger.c | 4 +
src/backend/commands/subscriptioncmds.c | 28 +++-
src/backend/replication/logical/ddltrigger.c | 6 +
src/backend/replication/logical/worker.c | 14 +-
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/ddldeparse.h | 11 +-
src/include/tcop/deparse_utility.h | 1 +
src/test/regress/expected/subscription.out | 152 +++++++++----------
14 files changed, 219 insertions(+), 95 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/ddldeparse.c b/src/backend/commands/ddldeparse.c
index 9a12a49005..6f53cca59d 100644
--- a/src/backend/commands/ddldeparse.c
+++ b/src/backend/commands/ddldeparse.c
@@ -263,6 +263,18 @@ new_jsonb_VA(JsonbParseState *state, int numobjs,...)
va_end(args);
}
+/*
+ * Process the role string into the output parse state.
+ */
+static void
+role_to_jsonb_element(JsonbParseState *state, char *owner)
+{
+ if (owner == NULL)
+ return;
+
+ new_jsonb_VA(state, 1, "myowner", jbvString, owner);
+}
+
/*
* A helper routine to insert jsonb for typId to the output parse state.
*/
@@ -1660,7 +1672,7 @@ deparse_withObj_ToJsonb(JsonbParseState *state, CreateStmt *node)
* %{tablespace}s
*/
static Jsonb *
-deparse_CreateStmt(Oid objectId, Node *parsetree)
+deparse_CreateStmt(Oid objectId, Node *parsetree, char *owner)
{
CreateStmt *node = (CreateStmt *) parsetree;
Relation relation = relation_open(objectId, AccessShareLock);
@@ -1677,6 +1689,10 @@ deparse_CreateStmt(Oid objectId, Node *parsetree)
/* mark the begin of ROOT object and start adding elements to it. */
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ /* create owner jsonb element */
+ role_to_jsonb_element(state, owner);
+
+ /* Start making fmt string */
appendStringInfoString(&fmtStr, "CREATE");
/* PERSISTENCE */
@@ -3276,7 +3292,7 @@ deparse_AlterTableStmt(CollectedCommand *cmd, ddl_deparse_context * context)
* CREATE %{persistence}s SEQUENCE %{identity}D %{definition: }s
*/
static Jsonb *
-deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree, char *owner)
{
Relation relation;
Form_pg_sequence seqform;
@@ -3299,6 +3315,10 @@ deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
/* mark the start of ROOT object */
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+
+ /* create owner jsonb element */
+ role_to_jsonb_element(state, owner);
+
appendStringInfoString(&fmtStr, "CREATE");
/* PERSISTENCE */
@@ -3378,7 +3398,7 @@ deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
* ALTER SEQUENCE %{identity}D %{definition: }s
*/
static Jsonb *
-deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree, char *owner)
{
Relation relation;
ListCell *cell;
@@ -3400,6 +3420,9 @@ deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
/* mark the start of ROOT object */
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
+ /* create owner jsonb element */
+ role_to_jsonb_element(state, owner);
+
new_jsonb_VA(state, 1,
"fmt", jbvString, "ALTER SEQUENCE %{identity}D %{definition: }s");
@@ -3660,10 +3683,11 @@ deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
* This function should cover all cases handled in ProcessUtilitySlow.
*/
static Jsonb *
-deparse_simple_command(CollectedCommand *cmd)
+deparse_simple_command(CollectedCommand *cmd, ddl_deparse_context * context)
{
Oid objectId;
Node *parsetree;
+ char *owner = context->include_owner ? cmd->role : NULL;
Assert(cmd->type == SCT_Simple);
@@ -3677,19 +3701,21 @@ deparse_simple_command(CollectedCommand *cmd)
switch (nodeTag(parsetree))
{
case T_AlterObjectSchemaStmt:
+ context->include_owner = false;
return deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
parsetree,
cmd->d.simple.secondaryObject);
case T_AlterSeqStmt:
- return deparse_AlterSeqStmt(objectId, parsetree);
+ return deparse_AlterSeqStmt(objectId, parsetree, owner);
case T_CreateSeqStmt:
- return deparse_CreateSeqStmt(objectId, parsetree);
+ return deparse_CreateSeqStmt(objectId, parsetree, owner);
case T_CreateStmt:
- return deparse_CreateStmt(objectId, parsetree);
+ return deparse_CreateStmt(objectId, parsetree, owner);
case T_RenameStmt:
+ context->include_owner = false;
return deparse_RenameStmt(cmd->d.simple.address, parsetree);
default:
@@ -3743,10 +3769,11 @@ deparse_utility_command(CollectedCommand *cmd, ddl_deparse_context * context)
switch (cmd->type)
{
case SCT_Simple:
- jsonb = deparse_simple_command(cmd);
+ jsonb = deparse_simple_command(cmd, context);
break;
case SCT_AlterTable:
jsonb = deparse_AlterTableStmt(cmd, context);
+ context->include_owner = false;
break;
default:
elog(ERROR, "unexpected deparse node type %d", cmd->type);
@@ -3780,6 +3807,8 @@ ddl_deparse_to_json(PG_FUNCTION_ARGS)
char *command;
ddl_deparse_context context;
+ context.include_owner = false;
+
/*
* Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
* the minimum volatility level.
diff --git a/src/backend/commands/ddljson.c b/src/backend/commands/ddljson.c
index d5c968b7c1..efb0b6152f 100644
--- a/src/backend/commands/ddljson.c
+++ b/src/backend/commands/ddljson.c
@@ -696,7 +696,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;
@@ -707,6 +707,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;
@@ -744,7 +765,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 15e243ca7e..01e0d59b93 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1488,6 +1488,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;
@@ -1524,6 +1525,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;
@@ -1807,6 +1809,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;
@@ -1838,6 +1841,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 54895ba929..9475e8439e 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);
/*
@@ -710,6 +724,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);
@@ -1132,7 +1147,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);
@@ -1211,6 +1227,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 6740d074fb..40df0f6c53 100644
--- a/src/backend/replication/logical/ddltrigger.c
+++ b/src/backend/replication/logical/ddltrigger.c
@@ -158,6 +158,8 @@ publication_deparse_table_rewrite(PG_FUNCTION_ARGS)
ddl_deparse_context context;
char *json_string;
+ context.include_owner = true;
+
/*
* Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
* the minimum volatility level.
@@ -249,6 +251,8 @@ publication_deparse_ddl_command_end(PG_FUNCTION_ARGS)
ddl_deparse_context context;
char *json_string;
+ context.include_owner = true;
+
/*
* Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
* the minimum volatility level.
@@ -328,6 +332,8 @@ publication_deparse_table_init_write(PG_FUNCTION_ARGS)
{
char *json_string;
+ context.include_owner = true;
+
/*
* Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
* the minimum volatility level.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 5d2cdce5ec..c233a365ba 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -3344,11 +3344,13 @@ 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;
+ UserContext ucxt;
const char *save_debug_query_string = debug_query_string;
message = logicalrep_read_ddl(s, &lsn, &prefix, &sz);
@@ -3363,9 +3365,16 @@ apply_handle_ddl(StringInfo s)
MemoryContextSwitchTo(ApplyMessageContext);
- ddl_command = deparse_ddl_json_to_string(message);
+ ddl_command = deparse_ddl_json_to_string(message, &owner);
debug_query_string = ddl_command;
+ /*
+ * If requested, set the current role to the owner that executed the
+ * command on the publication server.
+ */
+ if (MySubscription->matchddlowner && owner)
+ SwitchToUntrustedUser(get_role_oid(owner, false), &ucxt);
+
/* DestNone for logical replication */
receiver = CreateDestReceiver(DestNone);
parsetree_list = pg_parse_query(ddl_command);
@@ -3460,6 +3469,9 @@ apply_handle_ddl(StringInfo s)
MemoryContextDelete(per_parsetree_context);
}
+ if (MySubscription->matchddlowner && owner)
+ RestoreUserContext(&ucxt);
+
debug_query_string = save_debug_query_string;
CommandCounterIncrement();
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 334726cd0d..c7bc287836 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4607,6 +4607,7 @@ getSubscriptions(Archive *fout)
int i_subpublications;
int i_subbinary;
int i_subpasswordrequired;
+ int i_submatchddlowner;
int i,
ntups;
@@ -4661,11 +4662,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,
@@ -4695,6 +4698,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));
@@ -4727,6 +4731,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);
@@ -4805,6 +4811,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 7673d153aa..7752e94b6a 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 750ea19dc1..daaccb2663 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6505,7 +6505,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, false, false};
if (pset.sversion < 100000)
{
@@ -6564,10 +6564,12 @@ describeSubscriptions(const char *pattern, bool verbose)
appendPQExpBuffer(&buf,
", suborigin AS \"%s\"\n"
", subpasswordrequired AS \"%s\"\n"
- ", subrunasowner AS \"%s\"\n",
+ ", subrunasowner AS \"%s\"\n"
+ ", submatchddlowner AS \"%s\"\n",
gettext_noop("Origin"),
gettext_noop("Password required"),
- 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 1d40eebc78..c99ca2b509 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/ddldeparse.h b/src/include/tcop/ddldeparse.h
index caefb03bf6..3f111bc154 100644
--- a/src/include/tcop/ddldeparse.h
+++ b/src/include/tcop/ddldeparse.h
@@ -16,13 +16,22 @@
/* Context info needed for deparsing ddl command */
typedef struct
{
+ /*
+ * 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.
+ */
+ bool include_owner;
+
/* The maximum volatility of functions in expressions of a DDL command. */
char max_volatility;
} ddl_deparse_context;
extern char *deparse_utility_command(CollectedCommand *cmd,
ddl_deparse_context * context);
-extern char *deparse_ddl_json_to_string(char *jsonb);
+extern char *deparse_ddl_json_to_string(char *jsonb, char** owner);
extern char *deparse_drop_table(const char *objidentity, const char *objecttype,
Node *parsetree);
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 1831ec9aae..1f9cc953c6 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -47,6 +47,7 @@ typedef struct CollectedCommand
CollectedCommandType type;
bool in_extension;
+ char *role;
Node *parsetree;
union
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 3c1a0869ec..630f94da71 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | none | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | none | t | 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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -156,10 +156,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist2';
ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | f | f | off | dbname=regress_doesnotexist2 | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | 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 | f | t | off | dbname=regress_doesnotexist2 | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -174,10 +174,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | t | f | off | dbname=regress_doesnotexist2 | 0/12345
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | t | f | t | off | dbname=regress_doesnotexist2 | 0/12345
(1 row)
-- ok - with lsn = NONE
@@ -186,10 +186,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | t | f | off | dbname=regress_doesnotexist2 | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | t | f | t | off | dbname=regress_doesnotexist2 | 0/0
(1 row)
BEGIN;
@@ -221,10 +221,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | off | d | f | any | t | f | local | dbname=regress_doesnotexist2 | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | 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 | t | f | t | local | dbname=regress_doesnotexist2 | 0/0
(1 row)
-- rename back to keep the rest simple
@@ -253,19 +253,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | t | off | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | t | off | d | f | any | t | 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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
DROP SUBSCRIPTION regress_testsub;
@@ -277,27 +277,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | on | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | on | d | f | any | t | 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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | 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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
-- fail - publication already exists
@@ -312,10 +312,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub,testpub1,testpub2} | f | off | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | 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 | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
-- fail - publication used more than once
@@ -330,10 +330,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
DROP SUBSCRIPTION regress_testsub;
@@ -369,10 +369,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | p | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | p | f | any | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
--fail - alter of two_phase option not supported.
@@ -381,10 +381,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -394,10 +394,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -410,18 +410,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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | 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 | Password required | Run as owner? | Synchronous commit | Conninfo | Skip LSN
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | t | any | t | f | off | dbname=regress_doesnotexist | 0/0
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Match DDL owner | Synchronous commit | Conninfo | Skip LSN
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+-----------------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | t | any | t | f | t | off | dbname=regress_doesnotexist | 0/0
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
--
2.34.1
[application/octet-stream] 0004-DDL-replication-for-Table-DDL-commands-2023_06_22.patch (236.7K, 6-0004-DDL-replication-for-Table-DDL-commands-2023_06_22.patch)
download | inline diff:
From 4cea43d7e5c8a0799ecd6c40c693ee4b76c5c32e Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Wed, 21 Jun 2023 16:37:03 +0530
Subject: [PATCH 4/5] DDL replication for Table DDL commands
OVERVIEW
--------
1) To support DDL replication, we use the event trigger and DDL deparsing
facilities. During CREATE PUBLICATION we register a 'command end' trigger that
deparses the DDL (if the DDL is annotated as ddlreplok for DDL replication in
cmdtaglist.h) as a JSON blob, and WAL logs it. The event trigger is automatically
removed at the time of DROP PUBLICATION. The WALSender decodes the WAL and sends
it downstream similar to other DML commands. The subscriber then converts JSON
back to the DDL command string and executes it. In the subscriber, we also add
the newly added rel to pg_subscription_rel so that the DML changes on the new
table can be replicated without having to manually run
"ALTER SUBSCRIPTION ... REFRESH PUBLICATION".
2) Add subscription tap test for DDL replication for TABLE related commands.
For non-rewrite ALTER object command and CREATE object command:
---------------------------------------------------------------
We deparse the command at ddl_command_end event trigger and WAL log the
deparsed JSON string. The WALSender decodes the WAL and sends it to
subscriber if the created/altered table is published.
For DROP object:
----------------
The 'command start' event handler logs a ddl message with the relids of
the tables that are dropped which the output plugin (pgoutput) stores in
its internal data structure after verifying that it is for a table that is
part of the publication. Later the 'command end' event handler sends the
actual drop message. On receiving the 'command end', pgoutput sends the
DROP command only if it is for one of the relids marked for deletion.
The reason we have to do this is because, once the logical decoder
receives the 'command end' message, the relid of the table is no longer
valid as it has been deleted as part of invalidations received for the
drop table command. It is no longer possible to verify if the table is
part of the publication list or not. To make this possible, two more elements
are added to the ddl xlog and ddl message, (relid and cmdtype).
We could have also handled all this on the subscriber side, but
that would mean sending spurious ddl messages for tables that are not part
of the publication.
For table_rewrite ALTER TABLE command:
--------------------------------------
Executing a non-immutable expression during the table rewrite phase is not
allowed, as it may result in different data between publisher and subscriber.
While some may suggest converting the rewrite inserts to updates and replicate
them afte the ddl command to maintain data consistency. But it doesn't work if
the replica identity column is altered in the command. This is because the
rewrite inserts do not contain the old values and therefore cannot be converted
to update.
LIMIT:
Commands contain volatile functions are not allowed. Because
it's possible the functions contain DDL/DML in which case these operations
will be executed twice and cause duplicate data. In addition, we don't know
whether the tables being accessed by these DDL/DML are published or not. So
blindly allowing such functions can allow unintended clauses like the tables
accessed in those functions may not even exist on the subscriber.
---
contrib/test_decoding/expected/ddl.out | 27 ++
contrib/test_decoding/sql/ddl.sql | 6 +
contrib/test_decoding/test_decoding.c | 48 ++
doc/src/sgml/catalogs.sgml | 9 +
doc/src/sgml/logical-replication.sgml | 174 ++++++++
src/backend/access/rmgrdesc/Makefile | 1 +
.../access/rmgrdesc/logicalddlmsgdesc.c | 52 +++
src/backend/access/rmgrdesc/meson.build | 1 +
src/backend/access/transam/rmgr.c | 1 +
src/backend/catalog/pg_publication.c | 1 +
src/backend/commands/alter.c | 17 +
src/backend/commands/ddldeparse.c | 93 +++-
src/backend/commands/event_trigger.c | 56 ++-
src/backend/commands/publicationcmds.c | 228 +++++++++-
src/backend/replication/logical/Makefile | 2 +
src/backend/replication/logical/ddlmessage.c | 83 ++++
src/backend/replication/logical/ddltrigger.c | 349 +++++++++++++++
src/backend/replication/logical/decode.c | 41 ++
src/backend/replication/logical/logical.c | 97 +++-
.../replication/logical/logicalfuncs.c | 24 +
src/backend/replication/logical/meson.build | 2 +
src/backend/replication/logical/proto.c | 43 ++
.../replication/logical/reorderbuffer.c | 135 ++++++
src/backend/replication/logical/worker.c | 191 ++++++++
src/backend/replication/pgoutput/pgoutput.c | 251 ++++++++---
src/backend/tcop/cmdtag.c | 26 +-
src/backend/tcop/utility.c | 2 +-
src/backend/utils/cache/relcache.c | 1 +
src/bin/pg_dump/pg_dump.c | 26 +-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/pg_dump/t/002_pg_dump.pl | 2 +-
src/bin/pg_waldump/.gitignore | 1 +
src/bin/pg_waldump/rmgrdesc.c | 1 +
src/bin/psql/describe.c | 21 +-
src/include/access/rmgrlist.h | 1 +
src/include/catalog/pg_event_trigger.h | 1 +
src/include/catalog/pg_proc.dat | 20 +
src/include/catalog/pg_publication.h | 29 +-
src/include/commands/event_trigger.h | 8 +-
src/include/commands/publicationcmds.h | 14 +
src/include/replication/ddlmessage.h | 60 +++
src/include/replication/decode.h | 1 +
src/include/replication/logicalproto.h | 4 +
src/include/replication/output_plugin.h | 27 ++
src/include/replication/pgoutput.h | 1 +
src/include/replication/reorderbuffer.h | 39 ++
src/include/tcop/cmdtag.h | 4 +-
src/include/tcop/cmdtaglist.h | 386 ++++++++--------
src/include/tcop/ddldeparse.h | 15 +-
src/include/utils/rel.h | 2 +
src/test/regress/expected/psql.out | 6 +-
src/test/regress/expected/publication.out | 420 +++++++++---------
src/test/subscription/meson.build | 1 +
.../subscription/t/034_ddl_replication.pl | 374 ++++++++++++++++
src/tools/pgindent/typedefs.list | 6 +
55 files changed, 2905 insertions(+), 527 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
create mode 100644 src/test/subscription/t/034_ddl_replication.pl
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index d55fb3a667..c1cf38d4fa 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -831,6 +831,33 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
------
(0 rows)
+-- test pg_logical_emit_ddl_message with a practical payload
+SELECT 'ddl msg1' FROM pg_logical_emit_ddl_message('ddl msg1', 16394, 1, 'msg1');
+ ?column?
+----------
+ ddl msg1
+(1 row)
+
+SELECT 'ddl msg2' FROM pg_logical_emit_ddl_message('ddl msg2', 16394, 1, '{"fmt": "CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", "name": "foo", "authorization": {"fmt": "AUTHORIZATION %{authorization_role}I", "present": false, "authorization_role": null}, "if_not_exists": ""}');
+ ?column?
+----------
+ ddl msg2
+(1 row)
+
+SELECT 'ddl msg3' FROM pg_logical_emit_ddl_message('ddl msg3', 16394, 1, '{"fmt": "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D (%{table_elements:, }s) %{inherits}s %{tablespace}s %{on_commit}s %{partition_by}s %{access_method}s %{with_clause}s", "identity": {"objname": "foo", "schemaname": "element_test"}, "inherits": {"fmt": "INHERITS (%{parents:, }D)", "parents": null, "present": false}, "on_commit": {"fmt": "ON COMMIT %{on_commit_value}s", "present": false, "on_commit_value": null}, "tablespace": {"fmt": "TABLESPACE %{tablespace}I", "present": false, "tablespace": null}, "persistence": "", "with_clause": {"fmt": "WITH", "present": false}, "partition_by": {"fmt": "PARTITION BY %{definition}s", "present": false, "definition": null}, "access_method": {"fmt": "USING %{access_method}I", "present": false, "access_method": null}, "if_not_exists": "", "table_elements": [{"fmt": "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s %{not_null}s %{default}s %{generated_column}s", "name": "id", "type": "column", "coltype": {"typmod": "", "typarray": false, "typename": "int4", "schemaname": "pg_catalog"}, "default": {"fmt": "DEFAULT", "present": false}, "not_null": "", "collation": {"fmt": "COLLATE", "present": false}, "colstorage": "plain", "compression": {"fmt": "COMPRESSION %{compression_method}I", "present": false, "compression_method": null}, "generated_column": {"fmt": "GENERATED ALWAYS AS", "present": false}}]}}');
+ ?column?
+----------
+ ddl msg3
+(1 row)
+
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+ data
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ DDL message: prefix: ddl msg1, relid: 16394, cmdtype: Drop, sz: 4 content: msg1
+ DDL message: prefix: ddl msg2, relid: 16394, cmdtype: Drop, sz: 217 content: {"fmt": "CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", "name": "foo", "authorization": {"fmt": "AUTHORIZATION %{authorization_role}I", "present": false, "authorization_role": null}, "if_not_exists": ""}
+ DDL message: prefix: ddl msg3, relid: 16394, cmdtype: Drop, sz: 1396 content: {"fmt": "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D (%{table_elements:, }s) %{inherits}s %{tablespace}s %{on_commit}s %{partition_by}s %{access_method}s %{with_clause}s", "identity": {"objname": "foo", "schemaname": "element_test"}, "inherits": {"fmt": "INHERITS (%{parents:, }D)", "parents": null, "present": false}, "on_commit": {"fmt": "ON COMMIT %{on_commit_value}s", "present": false, "on_commit_value": null}, "tablespace": {"fmt": "TABLESPACE %{tablespace}I", "present": false, "tablespace": null}, "persistence": "", "with_clause": {"fmt": "WITH", "present": false}, "partition_by": {"fmt": "PARTITION BY %{definition}s", "present": false, "definition": null}, "access_method": {"fmt": "USING %{access_method}I", "present": false, "access_method": null}, "if_not_exists": "", "table_elements": [{"fmt": "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s %{not_null}s %{default}s %{generated_column}s", "name": "id", "type": "column", "coltype": {"typmod": "", "typarray": false, "typename": "int4", "schemaname": "pg_catalog"}, "default": {"fmt": "DEFAULT", "present": false}, "not_null": "", "collation": {"fmt": "COLLATE", "present": false}, "colstorage": "plain", "compression": {"fmt": "COMPRESSION %{compression_method}I", "present": false, "compression_method": null}, "generated_column": {"fmt": "GENERATED ALWAYS AS", "present": false}}]}}
+(3 rows)
+
SELECT pg_drop_replication_slot('regression_slot');
pg_drop_replication_slot
--------------------------
diff --git a/contrib/test_decoding/sql/ddl.sql b/contrib/test_decoding/sql/ddl.sql
index 57285a828c..592bc89d96 100644
--- a/contrib/test_decoding/sql/ddl.sql
+++ b/contrib/test_decoding/sql/ddl.sql
@@ -431,6 +431,12 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
-- done, free logical replication slot
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+-- test pg_logical_emit_ddl_message with a practical payload
+SELECT 'ddl msg1' FROM pg_logical_emit_ddl_message('ddl msg1', 16394, 1, 'msg1');
+SELECT 'ddl msg2' FROM pg_logical_emit_ddl_message('ddl msg2', 16394, 1, '{"fmt": "CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s", "name": "foo", "authorization": {"fmt": "AUTHORIZATION %{authorization_role}I", "present": false, "authorization_role": null}, "if_not_exists": ""}');
+SELECT 'ddl msg3' FROM pg_logical_emit_ddl_message('ddl msg3', 16394, 1, '{"fmt": "CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D (%{table_elements:, }s) %{inherits}s %{tablespace}s %{on_commit}s %{partition_by}s %{access_method}s %{with_clause}s", "identity": {"objname": "foo", "schemaname": "element_test"}, "inherits": {"fmt": "INHERITS (%{parents:, }D)", "parents": null, "present": false}, "on_commit": {"fmt": "ON COMMIT %{on_commit_value}s", "present": false, "on_commit_value": null}, "tablespace": {"fmt": "TABLESPACE %{tablespace}I", "present": false, "tablespace": null}, "persistence": "", "with_clause": {"fmt": "WITH", "present": false}, "partition_by": {"fmt": "PARTITION BY %{definition}s", "present": false, "definition": null}, "access_method": {"fmt": "USING %{access_method}I", "present": false, "access_method": null}, "if_not_exists": "", "table_elements": [{"fmt": "%{name}I %{coltype}T STORAGE %{colstorage}s %{compression}s %{collation}s %{not_null}s %{default}s %{generated_column}s", "name": "id", "type": "column", "coltype": {"typmod": "", "typarray": false, "typename": "int4", "schemaname": "pg_catalog"}, "default": {"fmt": "DEFAULT", "present": false}, "not_null": "", "collation": {"fmt": "COLLATE", "present": false}, "colstorage": "plain", "compression": {"fmt": "COMPRESSION %{compression_method}I", "present": false, "compression_method": null}, "generated_column": {"fmt": "GENERATED ALWAYS AS", "present": false}}]}}');
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+
SELECT pg_drop_replication_slot('regression_slot');
/* check that the slot is gone */
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index 12d1d0505d..9286f97258 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -14,9 +14,11 @@
#include "catalog/pg_type.h"
+#include "replication/ddlmessage.h"
#include "replication/logical.h"
#include "replication/origin.h"
+#include "tcop/ddldeparse.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -72,6 +74,12 @@ static void pg_decode_message(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn, XLogRecPtr lsn,
bool transactional, const char *prefix,
Size sz, const char *message);
+static void pg_decode_ddl_message(LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid,
+ DeparsedCommandType cmdtype,
+ Size sz, const char *message);
static bool pg_decode_filter_prepare(LogicalDecodingContext *ctx,
TransactionId xid,
const char *gid);
@@ -135,6 +143,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
cb->filter_by_origin_cb = pg_decode_filter;
cb->shutdown_cb = pg_decode_shutdown;
cb->message_cb = pg_decode_message;
+ cb->ddl_cb = pg_decode_ddl_message;
cb->filter_prepare_cb = pg_decode_filter_prepare;
cb->begin_prepare_cb = pg_decode_begin_prepare_txn;
cb->prepare_cb = pg_decode_prepare_txn;
@@ -750,6 +759,45 @@ pg_decode_message(LogicalDecodingContext *ctx,
OutputPluginWrite(ctx, true);
}
+static void
+pg_decode_ddl_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn, const char *prefix, Oid relid,
+ DeparsedCommandType cmdtype, Size sz, const char *message)
+{
+ OutputPluginPrepareWrite(ctx, true);
+ appendStringInfo(ctx->out, "DDL message: prefix: %s, relid: %u, ",
+ prefix, relid);
+
+ switch(cmdtype)
+ {
+ case DCT_SimpleCmd:
+ appendStringInfo(ctx->out, "cmdtype: Simple, ");
+ break;
+ case DCT_TableDropStart:
+ appendStringInfo(ctx->out, "cmdtype: Drop start, ");
+ break;
+ case DCT_TableDropEnd:
+ appendStringInfo(ctx->out, "cmdtype: Drop end, ");
+ break;
+ case DCT_TableAlter:
+ appendStringInfo(ctx->out, "cmdtype: Alter table, ");
+ break;
+ case DCT_ObjectCreate:
+ appendStringInfo(ctx->out, "cmdtype: Create, ");
+ break;
+ case DCT_ObjectDrop:
+ appendStringInfo(ctx->out, "cmdtype: Drop, ");
+ break;
+ default:
+ appendStringInfo(ctx->out, "cmdtype: Invalid, ");
+ break;
+ }
+
+ appendStringInfo(ctx->out, "sz: %zu content: ", sz);
+ appendBinaryStringInfo(ctx->out, message, sz);
+ OutputPluginWrite(ctx, true);
+}
+
static void
pg_decode_stream_start(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ed32ca0349..3568d070ab 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3863,6 +3863,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>evtisinternal</structfield> <type>bool</type>
+ </para>
+ <para>
+ True if the event trigger is internally generated.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>evttags</structfield> <type>text[]</type>
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 59cf92e6a9..a1da8bdd47 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1457,6 +1457,180 @@ test_sub=# SELECT * FROM t1 ORDER BY id;
</sect1>
+ <sect1 id="logical-replication-ddl">
+ <title>DDL Replication</title>
+ <para>
+ Data Definition Language (DDL) commands can be replicated using logical replication.
+ When enabled this feature automatically replicates supported DDL commands
+ that are successfully executed on a publisher to a subscriber. This is
+ especially useful if you have many schema changes over time that need replication.
+ </para>
+
+ <para>
+ For example, a CREATE TABLE command executed on the publisher gets
+ WAL-logged, and forwarded to the subscriber to replay; then an implicit "ALTER
+ SUBSCRIPTION ... REFRESH PUBLICATION" is performed on the subscriber database so any
+ following DML changes on the new table can be replicated.
+ </para>
+
+ <para>
+ DDL replication is disabled by default, it can be enabled using the ddl PUBLICATION
+ option. This option currently has one level and is only allowed to be set if the
+ PUBLICATION is FOR ALL TABLES or FOR TABLES IN SCHEMA.
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ table: this option enables replication of Table DDL commands,
+ which include:
+ <itemizedlist>
+ <listitem>
+ <para>
+ CREATE/ALTER/DROP TABLE
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ CREATE TABLE AS
+ </para>
+ </listitem>
+
+ </itemizedlist>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect2 id="ddl-replication-option-examples">
+ <title>Examples - Setup DDL Replication on the Publisher</title>
+
+ <para>
+ Enable TABLE DDL replication for an existing PUBLICATION:
+<programlisting>
+ALTER PUBLICATION mypub SET (ddl = 'table');
+</programlisting></para>
+
+ </sect2>
+
+ <sect2 id="ddl-replication-supported-commands">
+ <title>Supported DDL commands</title>
+
+ <para>
+ The DDL commands supported for logical replication are Table DDL commands,
+ include <literal>ALTER TABLE</literal>, <literal>CREATE TABLE</literal>,
+ <literal>CREATE TABLE AS</literal>, and <literal>DROP TABLE</literal>.
+ Other object commands and global commands are currently not
+ supported for replication. Global commands can be executed at any
+ database, include ROLE statements, Database statements, TableSpace
+ statements and some of the GrantStmt/RevokeStmt if the target object is a
+ global object. Temporary and unlogged table will not be replicated. User
+ should take care when creating these objects as these objects might be
+ required by the objects that are replicated (for example creation of
+ tables that might refer to an user-created tablespace will fail in the
+ subscriber if the user created tablespaces do not exist on the
+ subscriber).
+ </para>
+ </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
+ benefits of using the deparser output compared to the original command string
+ include:
+ <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>
+
+ <sect2 id="ddl-replication-ddl-restrictions">
+ <title>DDL Replication Restrictions</title>
+ <para>
+ DDL replication currently has the following restrictions.
+ <itemizedlist>
+ <listitem>
+ <para>
+ <command>ALTER TABLE</command> command which uses volatile functions
+ is not allowed.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ In <literal>ADD COLUMN ... DEFAULT</literal> clause and
+ <literal>ALTER COLUMN TYPE</literal> clause of <command>ALTER
+ TABLE</command> command, the functions and operators used in
+ expression must be immutable.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ The latter case can be worked around.
+ <itemizedlist>
+ <listitem>
+ <para>
+ To add a column with a non-immutable default value, first add a
+ column without a default value, then set a default value for the new
+ column, and update the value of the new column for existing rows.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ To change the column type, first add a new column of the desired
+ type, then update the new column value with the old column value,
+ and finnally drop the old column and rename the new column to the
+ old column.
+ </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 cd95eec37f..fd67f044e4 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -18,6 +18,7 @@ OBJS = \
gistdesc.o \
hashdesc.o \
heapdesc.o \
+ logicalddlmsgdesc.o \
logicalmsgdesc.o \
mxactdesc.o \
nbtdesc.o \
diff --git a/src/backend/access/rmgrdesc/logicalddlmsgdesc.c b/src/backend/access/rmgrdesc/logicalddlmsgdesc.c
new file mode 100644
index 0000000000..5f58e0fe51
--- /dev/null
+++ b/src/backend/access/rmgrdesc/logicalddlmsgdesc.c
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * logicalddlmsgdesc.c
+ * rmgr descriptor routines for replication/logical/ddlmessage.c
+ *
+ * Portions Copyright (c) 2015-2023, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/rmgrdesc/logicalddlmsgdesc.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "replication/ddlmessage.h"
+
+void
+logicalddlmsg_desc(StringInfo buf, XLogReaderState *record)
+{
+ char *rec = XLogRecGetData(record);
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info == XLOG_LOGICAL_DDL_MESSAGE)
+ {
+ xl_logical_ddl_message *xlrec = (xl_logical_ddl_message *) rec;
+ char *prefix = xlrec->message;
+ char *message = xlrec->message + xlrec->prefix_size;
+ char *sep = "";
+
+ Assert(prefix[xlrec->prefix_size - 1] == '\0');
+
+ appendStringInfo(buf, "prefix \"%s\"; payload (%zu bytes): ",
+ prefix, xlrec->message_size);
+ appendStringInfo(buf, "relid %u cmdtype %u", xlrec->relid, xlrec->cmdtype);
+ /* Write message payload as a series of hex bytes */
+ for (int cnt = 0; cnt < xlrec->message_size; cnt++)
+ {
+ appendStringInfo(buf, "%s%02X", sep, (unsigned char) message[cnt]);
+ sep = " ";
+ }
+ }
+}
+
+const char *
+logicalddlmsg_identify(uint8 info)
+{
+ if ((info & ~XLR_INFO_MASK) == XLOG_LOGICAL_DDL_MESSAGE)
+ return "DDL";
+
+ return NULL;
+}
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index f76e87e2d7..614fd7c1ac 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -11,6 +11,7 @@ rmgr_desc_sources = files(
'gistdesc.c',
'hashdesc.c',
'heapdesc.c',
+ 'logicalddlmsgdesc.c',
'logicalmsgdesc.c',
'mxactdesc.c',
'nbtdesc.c',
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 7d67eda5f7..678e81ae01 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
#include "fmgr.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "replication/ddlmessage.h"
#include "replication/decode.h"
#include "replication/message.h"
#include "replication/origin.h"
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index c488b6370b..d8a5940141 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1028,6 +1028,7 @@ GetPublication(Oid pubid)
pub->pubactions.pubupdate = pubform->pubupdate;
pub->pubactions.pubdelete = pubform->pubdelete;
pub->pubactions.pubtruncate = pubform->pubtruncate;
+ pub->pubactions.pubddl_table = pubform->pubddl_table;
pub->pubviaroot = pubform->pubviaroot;
ReleaseSysCache(tup);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index e95dc31bde..23435c9915 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -308,6 +308,23 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
/* Wake up related replication workers to handle this change quickly */
LogicalRepWorkersWakeupAtCommit(objectId);
}
+ else if (classId == EventTriggerRelationId)
+ {
+ Form_pg_event_trigger evtForm = (Form_pg_event_trigger) GETSTRUCT(oldtup);
+
+ if (SearchSysCacheExists1(EVENTTRIGGERNAME, CStringGetDatum(new_name)))
+ report_name_conflict(classId, new_name);
+
+ /*
+ * Event triggers created internally are not allowed to be altered by
+ * user.
+ */
+ if (evtForm->evtisinternal)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system event trigger",
+ NameStr(evtForm->evtname))));
+ }
else if (nameCacheId >= 0)
{
if (OidIsValid(namespaceId))
diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c
index 7f8e416d6a..9a12a49005 100644
--- a/src/backend/commands/ddldeparse.c
+++ b/src/backend/commands/ddldeparse.c
@@ -66,6 +66,26 @@
/* Estimated length of the generated jsonb string */
#define JSONB_ESTIMATED_LEN 128
+/*
+ * Mark the max_volatility flag for an expression in the command.
+ */
+static void
+mark_function_volatile(ddl_deparse_context * context, Node *expr)
+{
+ if (context->max_volatility == PROVOLATILE_VOLATILE)
+ return;
+
+ if (contain_volatile_functions(expr))
+ {
+ context->max_volatility = PROVOLATILE_VOLATILE;
+ return;
+ }
+
+ if (context->max_volatility == PROVOLATILE_IMMUTABLE &&
+ contain_mutable_functions(expr))
+ context->max_volatility = PROVOLATILE_STABLE;
+}
+
/*
* Return the string representation of the given RELPERSISTENCE value.
*/
@@ -94,7 +114,8 @@ get_persistence_str(char persistence)
* passed attribute has a default value.
*/
static char *
-RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
+RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext,
+ Node **expr)
{
Node *defval;
char *defstr;
@@ -104,6 +125,10 @@ RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
defstr = deparse_expression(defval, dpcontext, false, false);
+ /* Collect the expression for later replication safety checks */
+ if (expr)
+ *expr = defval;
+
return defstr;
}
@@ -772,7 +797,7 @@ deparse_ColumnIdentity_toJsonb(JsonbParseState *state, char *parentKey,
static void
deparse_ColumnDef_toJsonb(JsonbParseState *state, Relation relation,
List *dpcontext, bool composite, ColumnDef *coldef,
- bool is_alter)
+ bool is_alter, Node **expr)
{
Oid relid = RelationGetRelid(relation);
HeapTuple attrTup;
@@ -898,8 +923,7 @@ deparse_ColumnDef_toJsonb(JsonbParseState *state, Relation relation,
appendStringInfoString(&fmtStr, " %{default}s");
defstr = RelationGetColumnDefault(relation, attrForm->attnum,
- dpcontext);
-
+ dpcontext, expr);
insert_jsonb_key(state, "default");
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 2,
@@ -938,8 +962,7 @@ deparse_ColumnDef_toJsonb(JsonbParseState *state, Relation relation,
appendStringInfoString(&fmtStr, " %{generated_column}s");
defstr = RelationGetColumnDefault(relation, attrForm->attnum,
- dpcontext);
-
+ dpcontext, expr);
insert_jsonb_key(state, "generated_column");
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 2,
@@ -1075,8 +1098,7 @@ deparse_ColumnDef_typed_toJsonb(JsonbParseState *state, Relation relation,
appendStringInfoString(&fmtStr, " %{default}s");
defstr = RelationGetColumnDefault(relation, attrForm->attnum,
- dpcontext);
-
+ dpcontext, NULL);
insert_jsonb_key(state, "default");
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
new_jsonb_VA(state, 2,
@@ -1248,7 +1270,7 @@ deparse_TableElems_ToJsonb(JsonbParseState *state, Relation relation,
else
deparse_ColumnDef_toJsonb(state, relation, dpcontext,
composite, (ColumnDef *) elt,
- false);
+ false, NULL);
}
break;
case T_Constraint:
@@ -1929,12 +1951,13 @@ deparse_drop_table(const char *objidentity, const char *objecttype,
* ALTER %{objtype}s %{only}s %{identity}D %{subcmds:, }s
*/
static Jsonb *
-deparse_AlterTableStmt(CollectedCommand *cmd)
+deparse_AlterTableStmt(CollectedCommand *cmd, ddl_deparse_context * context)
{
List *dpcontext;
Relation rel;
ListCell *cell;
const char *reltype;
+ Node *expr = NULL;
Oid relId = cmd->d.alterTable.objectId;
AlterTableStmt *stmt = NULL;
StringInfoData fmtStr;
@@ -2061,7 +2084,8 @@ deparse_AlterTableStmt(CollectedCommand *cmd)
deparse_ColumnDef_toJsonb(state, rel, dpcontext,
false, (ColumnDef *) subcmd->def,
- true);
+ true, &expr);
+ mark_function_volatile(context, expr);
/* We have full fmt by now, so add jsonb element for that */
new_jsonb_VA(state, 1, "fmt", jbvString, fmtSub.data);
@@ -2164,7 +2188,7 @@ deparse_AlterTableStmt(CollectedCommand *cmd)
"column", jbvString, subcmd->name,
"definition", jbvString,
RelationGetColumnDefault(rel, attno,
- dpcontext_rel));
+ dpcontext_rel, NULL));
pushJsonbValue(&state, WJB_END_OBJECT, NULL);
ReleaseSysCache(attrtup);
@@ -2400,12 +2424,42 @@ deparse_AlterTableStmt(CollectedCommand *cmd)
{
/* XXX need to set the "recurse" bit somewhere? */
Oid constrOid = sub->address.objectId;
+ bool isnull;
+ HeapTuple tup;
+ Datum val;
+ Constraint *constr;
/* Skip adding constraint for inherits table sub command */
if (!OidIsValid(constrOid))
continue;
Assert(IsA(subcmd->def, Constraint));
+ constr = castNode(Constraint, subcmd->def);
+
+ if (!constr->skip_validation)
+ {
+ tup = SearchSysCache1(CONSTROID,
+ ObjectIdGetDatum(constrOid));
+
+ if (HeapTupleIsValid(tup))
+ {
+ char *conbin;
+
+ /* Fetch constraint expression in parsetree form */
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conbin,
+ &isnull);
+
+ if (!isnull)
+ {
+ conbin = TextDatumGetCString(val);
+ expr = stringToNode(conbin);
+ mark_function_volatile(context, expr);
+ }
+
+ ReleaseSysCache(tup);
+ }
+ }
/*
* Syntax: ADD CONSTRAINT %{name}I %{definition}s
@@ -2560,6 +2614,8 @@ deparse_AlterTableStmt(CollectedCommand *cmd)
"expression", jbvString,
sub->usingexpr);
pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+ mark_function_volatile(context, def->cooked_default);
+
}
/* We have full fmt by now, so add jsonb element for that */
@@ -3648,7 +3704,7 @@ deparse_simple_command(CollectedCommand *cmd)
* Workhorse to deparse a CollectedCommand.
*/
char *
-deparse_utility_command(CollectedCommand *cmd)
+deparse_utility_command(CollectedCommand *cmd, ddl_deparse_context * context)
{
OverrideSearchPath *overridePath;
MemoryContext oldcxt;
@@ -3690,7 +3746,7 @@ deparse_utility_command(CollectedCommand *cmd)
jsonb = deparse_simple_command(cmd);
break;
case SCT_AlterTable:
- jsonb = deparse_AlterTableStmt(cmd);
+ jsonb = deparse_AlterTableStmt(cmd, context);
break;
default:
elog(ERROR, "unexpected deparse node type %d", cmd->type);
@@ -3722,8 +3778,15 @@ ddl_deparse_to_json(PG_FUNCTION_ARGS)
{
CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
char *command;
+ ddl_deparse_context context;
+
+ /*
+ * Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
+ * the minimum volatility level.
+ */
+ context.max_volatility = PROVOLATILE_IMMUTABLE;
- command = deparse_utility_command(cmd);
+ command = deparse_utility_command(cmd, &context);
if (command)
PG_RETURN_TEXT_P(cstring_to_text(command));
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 8c2a494dcb..15e243ca7e 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -58,7 +58,7 @@ static void AlterEventTriggerOwner_internal(Relation rel,
static void error_duplicate_filter_variable(const char *defname);
static Datum filter_list_to_array(List *filterlist);
static Oid insert_event_trigger_tuple(const char *trigname, const char *eventname,
- Oid evtOwner, Oid funcoid, List *taglist);
+ Oid evtOwner, Oid funcoid, List *taglist, bool is_internal);
static void validate_ddl_tags(const char *filtervar, List *taglist);
static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
@@ -69,7 +69,7 @@ static const char *stringify_adefprivs_objtype(ObjectType objtype);
* Create an event trigger.
*/
Oid
-CreateEventTrigger(CreateEventTrigStmt *stmt)
+CreateEventTrigger(CreateEventTrigStmt *stmt, bool is_internal)
{
HeapTuple tuple;
Oid funcoid;
@@ -91,10 +91,10 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
errhint("Must be superuser to create an event trigger.")));
/* Validate event name. */
- if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
- strcmp(stmt->eventname, "ddl_command_end") != 0 &&
- strcmp(stmt->eventname, "sql_drop") != 0 &&
- strcmp(stmt->eventname, "table_rewrite") != 0)
+ if (strcmp(stmt->eventname, TRIG_DDL_CMD_START) != 0 &&
+ strcmp(stmt->eventname, TRIG_DDL_CMD_END) != 0 &&
+ strcmp(stmt->eventname, TRIG_TBL_CMD_DROP) != 0 &&
+ strcmp(stmt->eventname, TRIG_TBL_REWRITE) != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized event name \"%s\"",
@@ -118,12 +118,12 @@ 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)
+ if ((strcmp(stmt->eventname, TRIG_DDL_CMD_START) == 0 ||
+ strcmp(stmt->eventname, TRIG_DDL_CMD_END) == 0 ||
+ strcmp(stmt->eventname, TRIG_TBL_CMD_DROP) == 0)
&& tags != NULL)
validate_ddl_tags("tag", tags);
- else if (strcmp(stmt->eventname, "table_rewrite") == 0
+ else if (strcmp(stmt->eventname, TRIG_TBL_REWRITE) == 0
&& tags != NULL)
validate_table_rewrite_tags("tag", tags);
@@ -149,7 +149,7 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
/* Insert catalog entries. */
return insert_event_trigger_tuple(stmt->trigname, stmt->eventname,
- evtowner, funcoid, tags);
+ evtowner, funcoid, tags, is_internal);
}
/*
@@ -218,7 +218,7 @@ error_duplicate_filter_variable(const char *defname)
*/
static Oid
insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtOwner,
- Oid funcoid, List *taglist)
+ Oid funcoid, List *taglist, bool is_internal)
{
Relation tgrel;
Oid trigoid;
@@ -246,6 +246,7 @@ insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtO
values[Anum_pg_event_trigger_evtfoid - 1] = ObjectIdGetDatum(funcoid);
values[Anum_pg_event_trigger_evtenabled - 1] =
CharGetDatum(TRIGGER_FIRES_ON_ORIGIN);
+ values[Anum_pg_event_trigger_evtisinternal - 1] = BoolGetDatum(is_internal);
if (taglist == NIL)
nulls[Anum_pg_event_trigger_evttags - 1] = true;
else
@@ -524,6 +525,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
@@ -573,6 +575,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)
{
@@ -580,8 +588,26 @@ EventTriggerCommonSetup(Node *parsetree,
if (filter_event_trigger(tag, item))
{
- /* We must plan to fire this trigger. */
- runlist = lappend_oid(runlist, item->fnoid);
+ static const char *trigger_func_prefix = "publication_deparse_%s";
+ char trigger_func_name[NAMEDATALEN];
+ Oid pub_funcoid;
+ List *pub_funcname;
+
+ /* Get function oid of the publication's ddl deparse event trigger */
+ snprintf(trigger_func_name, sizeof(trigger_func_name), trigger_func_prefix,
+ eventstr);
+ pub_funcname = SystemFuncName(trigger_func_name);
+ pub_funcoid = LookupFuncName(pub_funcname, 0, NULL, true);
+
+ if (item->fnoid == pub_funcoid)
+ {
+ /* Only the first ddl deparse event trigger needs to be invoked */
+ if (pub_deparse_func_cnt++ == 0)
+ runlist = lappend_oid(runlist, item->fnoid);
+ }
+ else
+ runlist = lappend_oid(runlist, item->fnoid);
+
}
}
@@ -627,7 +653,7 @@ EventTriggerDDLCommandStart(Node *parsetree)
runlist = EventTriggerCommonSetup(parsetree,
EVT_DDLCommandStart,
- "ddl_command_start",
+ TRIG_DDL_CMD_START,
&trigdata);
if (runlist == NIL)
return;
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index f4ba572697..757e868a05 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -38,10 +38,12 @@
#include "commands/publicationcmds.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_clause.h"
#include "parser/parse_collate.h"
#include "parser/parse_relation.h"
+#include "parser/parser.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
@@ -85,18 +87,21 @@ parse_publication_options(ParseState *pstate,
bool *publish_given,
PublicationActions *pubactions,
bool *publish_via_partition_root_given,
- bool *publish_via_partition_root)
+ bool *publish_via_partition_root,
+ bool *ddl_type_given)
{
ListCell *lc;
*publish_given = false;
*publish_via_partition_root_given = false;
+ *ddl_type_given = false;
/* defaults */
pubactions->pubinsert = true;
pubactions->pubupdate = true;
pubactions->pubdelete = true;
pubactions->pubtruncate = true;
+ pubactions->pubddl_table = false;
*publish_via_partition_root = false;
/* Parse options */
@@ -158,6 +163,42 @@ parse_publication_options(ParseState *pstate,
*publish_via_partition_root_given = true;
*publish_via_partition_root = defGetBoolean(defel);
}
+ else if (strcmp(defel->defname, "ddl") == 0)
+ {
+ char *ddl_types;
+ List *ddl_type_list;
+ ListCell *lc2;
+
+ if (*ddl_type_given)
+ errorConflictingDefElem(defel, pstate);
+
+ /*
+ * If ddl option was given only the explicitly listed ddl types
+ * should be published.
+ */
+ pubactions->pubddl_table = false;
+
+ *ddl_type_given = true;
+ ddl_types = defGetString(defel);
+
+ if (!SplitIdentifierString(ddl_types, ',', &ddl_type_list))
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid list syntax for \"ddl\" option"));
+
+ /* Process the option list. */
+ foreach(lc2, ddl_type_list)
+ {
+ char *ddl_opt = (char *) lfirst(lc2);
+
+ if (strcmp(ddl_opt, "table") == 0)
+ pubactions->pubddl_table = true;
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized \"ddl\" value: \"%s\"", ddl_opt));
+ }
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -728,6 +769,130 @@ CheckPubRelationColumnList(char *pubname, List *tables,
}
}
+/*
+ * Helper function to create an event trigger for DDL replication.
+ */
+static void
+CreateDDLReplicaEventTrigger(char *eventname, List *commands, Oid puboid)
+{
+ List *tags = NIL;
+ ListCell *lc;
+ Oid trigger_id;
+ ObjectAddress referenced;
+ ObjectAddress pubaddress;
+ CreateEventTrigStmt *ddl_trigger;
+ char trigger_name[NAMEDATALEN];
+ char trigger_func_name[NAMEDATALEN];
+ static const char *trigger_func_format = "publication_deparse_%s";
+
+ ddl_trigger = makeNode(CreateEventTrigStmt);
+
+ snprintf(trigger_name, sizeof(trigger_name), PUB_EVENT_TRIG_FORMAT,
+ eventname, puboid);
+ snprintf(trigger_func_name, sizeof(trigger_func_name), trigger_func_format,
+ eventname);
+
+ ddl_trigger->trigname = pstrdup(trigger_name);
+ ddl_trigger->eventname = eventname;
+ ddl_trigger->funcname = SystemFuncName(trigger_func_name);
+
+ foreach(lc, commands)
+ {
+ CommandTag cmdtag = lfirst_int(lc);
+ String *tag = makeString(pstrdup(GetCommandTagName(cmdtag)));
+
+ tags = lappend(tags, tag);
+ }
+
+ ddl_trigger->whenclause = list_make1(makeDefElem("tag", (Node *) tags, -1));
+
+ trigger_id = CreateEventTrigger(ddl_trigger, true);
+
+ ObjectAddressSet(pubaddress, PublicationRelationId, puboid);
+
+ /*
+ * Register the event trigger as internally dependent on the publication.
+ */
+ ObjectAddressSet(referenced, EventTriggerRelationId, trigger_id);
+ recordDependencyOn(&referenced, &pubaddress, DEPENDENCY_INTERNAL);
+}
+
+/*
+ * If DDL replication is enabled, create event triggers to capture and log any
+ * relevant events.
+ */
+static void
+CreateDDLReplicaEventTriggers(PublicationActions pubactions, Oid puboid)
+{
+ List *start_commands = NIL;
+ List *rewrite_commands = NIL;
+ List *end_commands = NIL;
+
+ if (!pubactions.pubddl_table)
+ return;
+
+ start_commands = lappend_int(start_commands, CMDTAG_DROP_TABLE);
+ rewrite_commands = lappend_int(rewrite_commands, CMDTAG_ALTER_TABLE);
+
+ end_commands = lappend_int(end_commands, CMDTAG_CREATE_TABLE);
+ end_commands = lappend_int(end_commands, CMDTAG_ALTER_TABLE);
+ end_commands = lappend_int(end_commands, CMDTAG_DROP_TABLE);
+
+ /* Create the ddl_command_start event trigger */
+ if (start_commands != NIL)
+ CreateDDLReplicaEventTrigger(PUB_TRIG_DDL_CMD_START, start_commands, puboid);
+
+ /* Create the table_rewrite event trigger */
+ if (rewrite_commands != NIL)
+ CreateDDLReplicaEventTrigger(PUB_TRIG_TBL_REWRITE, rewrite_commands, puboid);
+
+ /* Create the ddl_command_end event trigger */
+ if (end_commands != NIL)
+ CreateDDLReplicaEventTrigger(PUB_TRIG_DDL_CMD_END, end_commands, puboid);
+}
+
+/*
+ * Helper function to drop an event trigger for DDL replication.
+ */
+static void
+DropDDLReplicaEventTrigger(char *eventname, Oid puboid)
+{
+ char trigger_name[NAMEDATALEN];
+ Oid evtoid;
+ ObjectAddress obj;
+
+ snprintf(trigger_name, sizeof(trigger_name), PUB_EVENT_TRIG_FORMAT,
+ eventname, puboid);
+
+ evtoid = get_event_trigger_oid(trigger_name, true);
+ if (!OidIsValid(evtoid))
+ return;
+
+ deleteDependencyRecordsForClass(EventTriggerRelationId, evtoid,
+ PublicationRelationId,
+ DEPENDENCY_INTERNAL);
+
+ /*
+ * Ensure that the dependency removal is visible, so that we can drop the
+ * event trigger.
+ */
+ CommandCounterIncrement();
+
+ ObjectAddressSet(obj, EventTriggerRelationId, evtoid);
+ performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+}
+
+/*
+ * Drop all the event triggers which are used for DDL replication.
+ */
+static void
+DropDDLReplicaEventTriggers(Oid puboid)
+{
+ DropDDLReplicaEventTrigger(TRIG_DDL_CMD_START, puboid);
+ DropDDLReplicaEventTrigger(TRIG_DDL_CMD_END, puboid);
+ DropDDLReplicaEventTrigger(TRIG_TBL_REWRITE, puboid);
+}
+
/*
* Create new publication.
*/
@@ -741,6 +906,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
Datum values[Natts_pg_publication];
HeapTuple tup;
bool publish_given;
+ bool ddl_type_given;
PublicationActions pubactions;
bool publish_via_partition_root_given;
bool publish_via_partition_root;
@@ -783,7 +949,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
stmt->options,
&publish_given, &pubactions,
&publish_via_partition_root_given,
- &publish_via_partition_root);
+ &publish_via_partition_root,
+ &ddl_type_given);
puboid = GetNewOidWithIndex(rel, PublicationObjectIndexId,
Anum_pg_publication_oid);
@@ -798,6 +965,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 +1004,11 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
{
List *rels;
+ if (pubactions.pubddl_table)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("DDL replication is only supported in FOR ALL TABLES or FOR TABLES IN SCHEMA publications"));
+
rels = OpenTableList(relations);
TransformPubWhereClauses(rels, pstate->p_sourcetext,
publish_via_partition_root);
@@ -858,6 +1032,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 +1061,7 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
bool replaces[Natts_pg_publication];
Datum values[Natts_pg_publication];
bool publish_given;
+ bool ddl_type_given;
PublicationActions pubactions;
bool publish_via_partition_root_given;
bool publish_via_partition_root;
@@ -890,13 +1070,14 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
List *root_relids = NIL;
ListCell *lc;
+ pubform = (Form_pg_publication) GETSTRUCT(tup);
+
parse_publication_options(pstate,
stmt->options,
&publish_given, &pubactions,
&publish_via_partition_root_given,
- &publish_via_partition_root);
-
- pubform = (Form_pg_publication) GETSTRUCT(tup);
+ &publish_via_partition_root,
+ &ddl_type_given);
/*
* If the publication doesn't publish changes via the root partitioned
@@ -978,6 +1159,18 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
}
}
+ if (ddl_type_given && pubactions.pubddl_table)
+ {
+ if (root_relids == NIL)
+ root_relids = GetPublicationRelations(pubform->oid,
+ PUBLICATION_PART_ROOT);
+
+ if (root_relids)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("DDL replication is only supported in FOR ALL TABLES or FOR TABLES IN SCHEMA publications"));
+ }
+
/* Everything ok, form a new tuple. */
memset(values, 0, sizeof(values));
memset(nulls, false, sizeof(nulls));
@@ -998,6 +1191,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 +1309,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 +1333,12 @@ AlterPublicationTables(AlterPublicationStmt *stmt, HeapTuple tup,
List *delrels = NIL;
ListCell *oldlc;
+ if (pubform->pubddl_table)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot add table to publication \"%s\" if DDL replication is enabled",
+ stmt->pubname));
+
TransformPubWhereClauses(rels, queryString, pubform->pubviaroot);
CheckPubRelationColumnList(stmt->pubname, rels, publish_schema,
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..b79ddd8cdc 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -16,6 +16,8 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
OBJS = \
applyparallelworker.o \
+ ddlmessage.o \
+ ddltrigger.o \
decode.o \
launcher.o \
logical.o \
diff --git a/src/backend/replication/logical/ddlmessage.c b/src/backend/replication/logical/ddlmessage.c
new file mode 100644
index 0000000000..3f144de395
--- /dev/null
+++ b/src/backend/replication/logical/ddlmessage.c
@@ -0,0 +1,83 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddlmessage.c
+ * Logical DDL messages.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/replication/logical/ddlmessage.c
+ *
+ * NOTES
+ *
+ * Logical DDL messages allow XLOG logging of DDL command strings that
+ * get passed to the logical decoding plugin. In normal XLOG processing they
+ * are same as NOOP.
+ *
+ * Unlike generic logical messages, these DDL messages have only transactional
+ * mode. Note by default DDLs in PostgreSQL are transactional.
+ *
+ * These messages are part of current transaction and will be sent to
+ * decoding plugin similar to DML operations.
+ *
+ * Every message includes a prefix to avoid conflicts between different decoding
+ * plugins. Plugin authors must take special care to use a unique prefix (e.g one
+ * idea is to include the name of the extension).
+ * ---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "access/xloginsert.h"
+#include "catalog/namespace.h"
+#include "miscadmin.h"
+#include "nodes/execnodes.h"
+#include "replication/logical.h"
+#include "replication/ddlmessage.h"
+#include "utils/memutils.h"
+
+/*
+ * Write logical decoding DDL message into XLog.
+ */
+XLogRecPtr
+LogLogicalDDLMessage(const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ const char *message, size_t size)
+{
+ xl_logical_ddl_message xlrec;
+
+ /* Ensure we have a valid transaction id. */
+ Assert(IsTransactionState());
+ GetCurrentTransactionId();
+
+ xlrec.dbId = MyDatabaseId;
+ /* Trailing zero is critical; see logicalddlmsg_desc */
+ xlrec.prefix_size = strlen(prefix) + 1;
+ xlrec.message_size = size;
+ xlrec.relid = relid;
+ xlrec.cmdtype = cmdtype;
+
+ XLogBeginInsert();
+ XLogRegisterData((char *) &xlrec, SizeOfLogicalDDLMessage);
+ XLogRegisterData(unconstify(char *, prefix), xlrec.prefix_size);
+ XLogRegisterData(unconstify(char *, message), size);
+
+ /* Allow origin filtering */
+ XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+ return XLogInsert(RM_LOGICALDDLMSG_ID, XLOG_LOGICAL_DDL_MESSAGE);
+}
+
+/*
+ * Redo is basically just noop for logical decoding DDL messages.
+ */
+void
+logicalddlmsg_redo(XLogReaderState *record)
+{
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+
+ if (info != XLOG_LOGICAL_DDL_MESSAGE)
+ elog(PANIC, "logicalddlmsg_redo: unknown op code %u", info);
+
+ /* This is only interesting for logical decoding, see decode.c. */
+}
diff --git a/src/backend/replication/logical/ddltrigger.c b/src/backend/replication/logical/ddltrigger.c
new file mode 100644
index 0000000000..6740d074fb
--- /dev/null
+++ b/src/backend/replication/logical/ddltrigger.c
@@ -0,0 +1,349 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddltrigger.c
+ * Logical DDL triggers.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/replication/logical/ddltrigger.c
+ *
+ * NOTES
+ *
+ * Deparse the ddl command and log it.
+ *
+ * ---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/table.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_proc.h"
+#include "commands/event_trigger.h"
+#include "funcapi.h"
+#include "lib/ilist.h"
+#include "replication/ddlmessage.h"
+#include "tcop/ddldeparse.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+
+extern EventTriggerQueryState *currentEventTriggerState;
+
+
+/*
+ * Check if the command can be published.
+ *
+ * XXX Executing a non-immutable expression during the table rewrite phase is
+ * not allowed, as it may result in different data between publisher and
+ * subscriber. While some may suggest converting the rewrite inserts to updates
+ * and replicate them after the ddl command to maintain data consistency, but it
+ * doesn't work if the replica identity column is altered in the command. This
+ * is because the rewrite inserts do not contain the old values and therefore
+ * cannot be converted to update.
+ *
+ * Apart from that, commands containing volatile functions are not allowed. Because
+ * it's possible the functions contain DDL/DML in which case these operations
+ * will be executed twice and cause duplicate data. In addition, we don't know
+ * whether the tables being accessed by these DDL/DML are published or not. So
+ * blindly allowing such functions can allow unintended clauses like the tables
+ * accessed in those functions may not even exist on the subscriber.
+ */
+static void
+check_command_publishable(ddl_deparse_context context, bool is_rewrite)
+{
+
+ if (is_rewrite && context.max_volatility != PROVOLATILE_IMMUTABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot rewrite table if this command contains mutable function because it cannot be replicated in DDL replication"));
+
+ if (context.max_volatility == PROVOLATILE_VOLATILE)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot use volatile function in this command because it cannot be replicated in DDL replication"));
+}
+
+/*
+ * Deparse the ddl command and log it prior to
+ * execution. Currently only used for DROP TABLE command
+ * so that catalog can be accessed before being deleted.
+ * This is to check if the table is part of the publication
+ * or not.
+ */
+Datum
+publication_deparse_ddl_command_start(PG_FUNCTION_ARGS)
+{
+ EventTriggerData *trigdata;
+ char *command = psprintf("Drop table command start");
+ DropStmt *stmt;
+ ListCell *cell1;
+
+ if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+ elog(ERROR, "not fired by event trigger manager");
+
+ trigdata = (EventTriggerData *) fcinfo->context;
+ stmt = (DropStmt *) trigdata->parsetree;
+
+ /* Extract the relid from the parse tree */
+ foreach(cell1, stmt->objects)
+ {
+ char relpersist;
+ Node *object = lfirst(cell1);
+ ObjectAddress address;
+ Relation relation = NULL;
+
+ address = get_object_address(stmt->removeType,
+ object,
+ &relation,
+ AccessExclusiveLock,
+ true);
+
+ /* Object does not exist, nothing to do */
+ if (!relation)
+ continue;
+
+ relpersist = get_rel_persistence(address.objectId);
+
+ /*
+ * Do not generate wal log for commands whose target table is a
+ * temporary or unlogged table.
+ *
+ * XXX We may generate wal logs for unlogged tables in the future so
+ * that unlogged tables can also be created and altered on the
+ * subscriber side. This makes it possible to directly replay the SET
+ * LOGGED command and the incoming rewrite message without creating a
+ * new table.
+ */
+ if (relpersist == RELPERSISTENCE_PERMANENT)
+ LogLogicalDDLMessage("deparse", address.objectId, DCT_TableDropStart,
+ command, strlen(command) + 1);
+
+ table_close(relation, NoLock);
+ }
+ return PointerGetDatum(NULL);
+}
+
+/*
+ * publication_deparse_table_rewrite
+ *
+ * Deparse the ddl table rewrite command and log it.
+ */
+Datum
+publication_deparse_table_rewrite(PG_FUNCTION_ARGS)
+{
+ char relpersist;
+ CollectedCommand *cmd;
+
+ if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+ elog(ERROR, "not fired by event trigger manager");
+
+ cmd = currentEventTriggerState->currentCommand;
+
+ Assert(cmd && cmd->d.alterTable.rewrite);
+
+ relpersist = get_rel_persistence(cmd->d.alterTable.objectId);
+
+ /*
+ * Do not generate wal log for commands whose target table is a temporary
+ * or unlogged table.
+ *
+ * XXX We may generate wal logs for unlogged tables in the future so that
+ * unlogged tables can also be created and altered on the subscriber side.
+ * This makes it possible to directly replay the SET LOGGED command and the
+ * incoming rewrite message without creating a new table.
+ */
+ if (relpersist == RELPERSISTENCE_PERMANENT)
+ {
+ ddl_deparse_context context;
+ char *json_string;
+
+ /*
+ * Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
+ * the minimum volatility level.
+ */
+ context.max_volatility = PROVOLATILE_IMMUTABLE;
+
+ /* Deparse the DDL command and WAL log it to allow decoding of the same. */
+ json_string = deparse_utility_command(cmd, &context);
+
+ if (json_string != NULL)
+ {
+ check_command_publishable(context, true);
+ LogLogicalDDLMessage("deparse", cmd->d.alterTable.objectId, DCT_TableAlter,
+ json_string, strlen(json_string) + 1);
+ }
+ }
+
+ return PointerGetDatum(NULL);
+}
+
+/*
+ * Deparse the ddl command and log it. This function
+ * is called after the execution of the command but before the
+ * transaction commits.
+ */
+Datum
+publication_deparse_ddl_command_end(PG_FUNCTION_ARGS)
+{
+ ListCell *lc;
+ slist_iter iter;
+ Oid relid;
+ char relkind;
+
+ if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+ elog(ERROR, "not fired by event trigger manager");
+
+ foreach(lc, currentEventTriggerState->commandList)
+ {
+ char relpersist = RELPERSISTENCE_PERMANENT;
+ CollectedCommand *cmd = lfirst(lc);
+ DeparsedCommandType cmdtype;
+
+ /* Rewrite DDL has been handled in table_rewrite trigger */
+ if (cmd->d.alterTable.rewrite)
+ {
+ RenameStmt *renameStmt = (RenameStmt *) cmd->parsetree;
+
+ if (renameStmt && renameStmt->relationType != OBJECT_TYPE &&
+ renameStmt->relationType != OBJECT_TABLE)
+ continue;
+ }
+
+ if (cmd->type == SCT_Simple &&
+ !OidIsValid(cmd->d.simple.address.objectId))
+ continue;
+
+ if (cmd->type == SCT_AlterTable)
+ {
+ relid = cmd->d.alterTable.objectId;
+ cmdtype = DCT_TableAlter;
+ }
+ else
+ {
+ /* Only SCT_Simple for now */
+ relid = cmd->d.simple.address.objectId;
+ cmdtype = DCT_SimpleCmd;
+ }
+
+ relkind = get_rel_relkind(relid);
+ if (relkind)
+ relpersist = get_rel_persistence(relid);
+
+ /*
+ * Do not generate wal log for commands whose target table is a
+ * temporary or unlogged table.
+ *
+ * XXX We may generate wal logs for unlogged tables in the future so
+ * that unlogged tables can also be created and altered on the
+ * subscriber side. This makes it possible to directly replay the SET
+ * LOGGED command and the incoming rewrite message without creating a
+ * new table.
+ */
+ if (relpersist == RELPERSISTENCE_PERMANENT)
+ {
+ /*
+ * Deparse the DDL command and WAL log it to allow decoding of the
+ * same.
+ */
+ ddl_deparse_context context;
+ char *json_string;
+
+ /*
+ * Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
+ * the minimum volatility level.
+ */
+ context.max_volatility = PROVOLATILE_IMMUTABLE;
+
+ json_string = deparse_utility_command(cmd, &context);
+
+ if (json_string != NULL)
+ {
+ check_command_publishable(context, false);
+ LogLogicalDDLMessage("deparse", relid, cmdtype, json_string,
+ strlen(json_string) + 1);
+ }
+ }
+ }
+
+ /* Drop commands are not part commandlist but handled here as part of SQLDropList */
+ slist_foreach(iter, &(currentEventTriggerState->SQLDropList))
+ {
+ SQLDropObject *obj;
+ EventTriggerData *trigdata;
+
+ trigdata = (EventTriggerData *) fcinfo->context;
+
+ obj = slist_container(SQLDropObject, next, iter.cur);
+
+ if (!obj->original)
+ continue;
+
+ if (strcmp(obj->objecttype, "table") == 0)
+ {
+ DeparsedCommandType cmdtype = DCT_TableDropEnd;
+ char *command;
+
+ command = deparse_drop_table(obj->objidentity, obj->objecttype,
+ trigdata->parsetree);
+ if (command)
+ LogLogicalDDLMessage("deparse", obj->address.objectId, cmdtype,
+ command, strlen(command) + 1);
+ }
+ }
+
+ return PointerGetDatum(NULL);
+}
+
+/*
+ * publication_deparse_table_init_write
+ *
+ * Deparse the ddl table create command and log it.
+ */
+Datum
+publication_deparse_table_init_write(PG_FUNCTION_ARGS)
+{
+ char relpersist;
+ CollectedCommand *cmd;
+ ddl_deparse_context context;
+
+ if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
+ elog(ERROR, "not fired by event trigger manager");
+
+ cmd = currentEventTriggerState->currentCommand;
+ Assert(cmd);
+
+ relpersist = get_rel_persistence(cmd->d.simple.address.objectId);
+
+ /*
+ * Do not generate wal log for commands whose target table is a temporary
+ * table.
+ *
+ * We will generate wal logs for unlogged tables so that unlogged tables
+ * can also be created and altered on the subscriber side. This makes it
+ * possible to directly replay the SET LOGGED command and the incoming
+ * rewrite message without creating a new table.
+ */
+ if (relpersist == RELPERSISTENCE_PERMANENT)
+ {
+ char *json_string;
+
+ /*
+ * Initialize the max_volatility flag to PROVOLATILE_IMMUTABLE, which is
+ * the minimum volatility level.
+ */
+ context.max_volatility = PROVOLATILE_IMMUTABLE;
+
+ /* Deparse the DDL command and WAL log it to allow decoding of the same. */
+ json_string = deparse_utility_command(cmd, &context);
+
+ if (json_string != NULL)
+ {
+ check_command_publishable(context, false);
+ LogLogicalDDLMessage("deparse", cmd->d.simple.address.objectId, DCT_SimpleCmd,
+ json_string, strlen(json_string) + 1);
+ }
+ }
+
+ return PointerGetDatum(NULL);
+}
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index d91055a440..b22bbcd15a 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -36,6 +36,7 @@
#include "access/xlogutils.h"
#include "catalog/pg_control.h"
#include "replication/decode.h"
+#include "replication/ddlmessage.h"
#include "replication/logical.h"
#include "replication/message.h"
#include "replication/origin.h"
@@ -641,6 +642,46 @@ logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
message->message + message->prefix_size);
}
+/*
+ * Handle rmgr LOGICALDDLMSG_ID records for DecodeRecordIntoReorderBuffer().
+ */
+void
+logicalddl_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+{
+ SnapBuild *builder = ctx->snapshot_builder;
+ XLogReaderState *r = buf->record;
+ TransactionId xid = XLogRecGetXid(r);
+ uint8 info = XLogRecGetInfo(r) & ~XLR_INFO_MASK;
+ RepOriginId origin_id = XLogRecGetOrigin(r);
+ xl_logical_ddl_message *message;
+
+ if (info != XLOG_LOGICAL_DDL_MESSAGE)
+ elog(ERROR, "unexpected RM_LOGICALDDLMSG_ID record type: %u", info);
+
+ ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(r), buf->origptr);
+
+ /*
+ * If we don't have snapshot or we are just fast-forwarding, there is no
+ * point in decoding ddl messages.
+ */
+ if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT ||
+ ctx->fast_forward)
+ return;
+
+ message = (xl_logical_ddl_message *) XLogRecGetData(r);
+
+ if (message->dbId != ctx->slot->data.database ||
+ FilterByOrigin(ctx, origin_id))
+ return;
+
+ if (SnapBuildProcessChange(builder, xid, buf->origptr))
+ ReorderBufferQueueDDLMessage(ctx->reorder, xid, buf->endptr,
+ message->message, /* first part of message is prefix */
+ message->message_size,
+ message->message + message->prefix_size,
+ message->relid, message->cmdtype);
+}
+
/*
* Consolidated commit record handling between the different form of commit
* records.
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 41243d0187..1d99fac116 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -33,6 +33,7 @@
#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "replication/ddlmessage.h"
#include "replication/decode.h"
#include "replication/logical.h"
#include "replication/origin.h"
@@ -73,6 +74,10 @@ static void truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
static void message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
XLogRecPtr message_lsn, bool transactional,
const char *prefix, Size message_size, const char *message);
+static void ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn, const char *prefix,
+ Oid relid, DeparsedCommandType cmdtype,
+ Size message_size, const char *message);
/* streaming callbacks */
static void stream_start_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
@@ -90,6 +95,11 @@ static void stream_change_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn
static void stream_message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
XLogRecPtr message_lsn, bool transactional,
const char *prefix, Size message_size, const char *message);
+static void stream_ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid, DeparsedCommandType cmdtype,
+ Size message_size, const char *message);
static void stream_truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
int nrelations, Relation relations[], ReorderBufferChange *change);
@@ -221,10 +231,11 @@ StartupDecodingContext(List *output_plugin_options,
ctx->reorder->apply_truncate = truncate_cb_wrapper;
ctx->reorder->commit = commit_cb_wrapper;
ctx->reorder->message = message_cb_wrapper;
+ ctx->reorder->ddl = ddl_cb_wrapper;
/*
* To support streaming, we require start/stop/abort/commit/change
- * callbacks. The message and truncate callbacks are optional, similar to
+ * callbacks. The message, ddl and truncate callbacks are optional, similar to
* regular output plugins. We however enable streaming when at least one
* of the methods is enabled so that we can easily identify missing
* methods.
@@ -237,12 +248,13 @@ StartupDecodingContext(List *output_plugin_options,
(ctx->callbacks.stream_commit_cb != NULL) ||
(ctx->callbacks.stream_change_cb != NULL) ||
(ctx->callbacks.stream_message_cb != NULL) ||
+ (ctx->callbacks.stream_ddl_cb != NULL) ||
(ctx->callbacks.stream_truncate_cb != NULL);
/*
* streaming callbacks
*
- * stream_message and stream_truncate callbacks are optional, so we do not
+ * stream_message, stream_ddl and stream_truncate callbacks are optional, so we do not
* fail with ERROR when missing, but the wrappers simply do nothing. We
* must set the ReorderBuffer callbacks to something, otherwise the calls
* from there will crash (we don't want to move the checks there).
@@ -254,6 +266,7 @@ StartupDecodingContext(List *output_plugin_options,
ctx->reorder->stream_commit = stream_commit_cb_wrapper;
ctx->reorder->stream_change = stream_change_cb_wrapper;
ctx->reorder->stream_message = stream_message_cb_wrapper;
+ ctx->reorder->stream_ddl = stream_ddl_cb_wrapper;
ctx->reorder->stream_truncate = stream_truncate_cb_wrapper;
@@ -1260,6 +1273,44 @@ message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
error_context_stack = errcallback.previous;
}
+static void
+ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message)
+{
+ LogicalDecodingContext *ctx = cache->private_data;
+ LogicalErrorCallbackState state;
+ ErrorContextCallback errcallback;
+
+ Assert(!ctx->fast_forward);
+
+ if (ctx->callbacks.ddl_cb == NULL)
+ return;
+
+ /* Push callback + info on the error context stack */
+ state.ctx = ctx;
+ state.callback_name = "ddl";
+ state.report_location = message_lsn;
+ errcallback.callback = output_plugin_error_callback;
+ errcallback.arg = (void *) &state;
+ errcallback.previous = error_context_stack;
+ error_context_stack = &errcallback;
+
+ /* set output state */
+ ctx->accept_writes = true;
+ ctx->write_xid = txn->xid;
+ ctx->write_location = message_lsn;
+
+ /* do the actual work: call callback */
+ ctx->callbacks.ddl_cb(ctx, txn, message_lsn, prefix, relid, cmdtype,
+ message_size, message);
+
+ /* Pop the error context stack */
+ error_context_stack = errcallback.previous;
+}
+
static void
stream_start_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
XLogRecPtr first_lsn)
@@ -1575,6 +1626,48 @@ stream_message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
error_context_stack = errcallback.previous;
}
+static void
+stream_ddl_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message)
+{
+ LogicalDecodingContext *ctx = cache->private_data;
+ LogicalErrorCallbackState state;
+ ErrorContextCallback errcallback;
+
+ Assert(!ctx->fast_forward);
+
+ /* We're only supposed to call this when streaming is supported. */
+ Assert(ctx->streaming);
+
+ /* this callback is optional */
+ if (ctx->callbacks.stream_ddl_cb == NULL)
+ return;
+
+ /* Push callback + info on the error context stack */
+ state.ctx = ctx;
+ state.callback_name = "stream_ddl";
+ state.report_location = message_lsn;
+ errcallback.callback = output_plugin_error_callback;
+ errcallback.arg = (void *) &state;
+ errcallback.previous = error_context_stack;
+ error_context_stack = &errcallback;
+
+ /* set output state */
+ ctx->accept_writes = true;
+ ctx->write_xid = txn->xid;
+ ctx->write_location = message_lsn;
+
+ /* do the actual work: call callback */
+ ctx->callbacks.stream_ddl_cb(ctx, txn, message_lsn, prefix, relid,
+ cmdtype, message_size, message);
+
+ /* Pop the error context stack */
+ error_context_stack = errcallback.previous;
+}
+
static void
stream_truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
int nrelations, Relation relations[],
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 55a24c02c9..bececc3f93 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -29,6 +29,7 @@
#include "nodes/makefuncs.h"
#include "replication/decode.h"
#include "replication/logical.h"
+#include "replication/ddlmessage.h"
#include "replication/message.h"
#include "storage/fd.h"
#include "utils/array.h"
@@ -375,3 +376,26 @@ pg_logical_emit_message_text(PG_FUNCTION_ARGS)
/* bytea and text are compatible */
return pg_logical_emit_message_bytea(fcinfo);
}
+
+/*
+ * SQL function for writing logical decoding DDL message into WAL.
+ */
+Datum
+pg_logical_emit_ddl_message_bytea(PG_FUNCTION_ARGS)
+{
+ char *prefix = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ Oid relid = PG_GETARG_OID(1);
+ DeparsedCommandType cmdtype = PG_GETARG_INT16(2);
+ char *data = text_to_cstring(PG_GETARG_TEXT_PP(3));
+ XLogRecPtr lsn;
+
+ lsn = LogLogicalDDLMessage(prefix, relid, cmdtype, data, strlen(data));
+ PG_RETURN_LSN(lsn);
+}
+
+Datum
+pg_logical_emit_ddl_message_text(PG_FUNCTION_ARGS)
+{
+ /* bytea and text are compatible */
+ return pg_logical_emit_ddl_message_bytea(fcinfo);
+}
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..05e775697e 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -2,6 +2,8 @@
backend_sources += files(
'applyparallelworker.c',
+ 'ddlmessage.c',
+ 'ddltrigger.c',
'decode.c',
'launcher.c',
'logical.c',
diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
index f308713275..7ec3b71662 100644
--- a/src/backend/replication/logical/proto.c
+++ b/src/backend/replication/logical/proto.c
@@ -663,6 +663,47 @@ logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn,
pq_sendbytes(out, message, sz);
}
+/*
+ * Read DDL from stream
+ */
+char *
+logicalrep_read_ddl(StringInfo in, XLogRecPtr *lsn,
+ const char **prefix,
+ Size *sz)
+{
+ uint8 flags;
+ char *msg;
+
+ flags = pq_getmsgint(in, 1);
+ if (flags != 0)
+ elog(ERROR, "unrecognized flags %u in ddl message", flags);
+
+ *lsn = pq_getmsgint64(in);
+ *prefix = pq_getmsgstring(in);
+ *sz = pq_getmsgint(in, 4);
+ msg = (char *) pq_getmsgbytes(in, *sz);
+
+ return msg;
+}
+
+/*
+ * Write DDL to stream
+ */
+void
+logicalrep_write_ddl(StringInfo out, XLogRecPtr lsn,
+ const char *prefix, Size sz, const char *message)
+{
+ uint8 flags = 0;
+
+ pq_sendbyte(out, LOGICAL_REP_MSG_DDL);
+
+ pq_sendint8(out, flags);
+ pq_sendint64(out, lsn);
+ pq_sendstring(out, prefix);
+ pq_sendint32(out, sz);
+ pq_sendbytes(out, message, sz);
+}
+
/*
* Write relation description to the output stream.
*/
@@ -1238,6 +1279,8 @@ logicalrep_message_type(LogicalRepMsgType action)
return "TYPE";
case LOGICAL_REP_MSG_MESSAGE:
return "MESSAGE";
+ case LOGICAL_REP_MSG_DDL:
+ return "DDL";
case LOGICAL_REP_MSG_BEGIN_PREPARE:
return "BEGIN PREPARE";
case LOGICAL_REP_MSG_PREPARE:
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 26d252bd87..d21042e725 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -94,6 +94,7 @@
#include "lib/binaryheap.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "replication/ddlmessage.h"
#include "replication/logical.h"
#include "replication/reorderbuffer.h"
#include "replication/slot.h"
@@ -516,6 +517,14 @@ ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change,
pfree(change->data.msg.message);
change->data.msg.message = NULL;
break;
+ case REORDER_BUFFER_CHANGE_DDL:
+ if (change->data.ddl.prefix != NULL)
+ pfree(change->data.ddl.prefix);
+ change->data.ddl.prefix = NULL;
+ if (change->data.ddl.message != NULL)
+ pfree(change->data.ddl.message);
+ change->data.ddl.message = NULL;
+ break;
case REORDER_BUFFER_CHANGE_INVALIDATION:
if (change->data.inval.invalidations)
pfree(change->data.inval.invalidations);
@@ -895,6 +904,36 @@ ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
}
}
+/*
+ * A transactional DDL message is queued to be processed upon commit.
+ */
+void
+ReorderBufferQueueDDLMessage(ReorderBuffer *rb, TransactionId xid,
+ XLogRecPtr lsn, const char *prefix,
+ Size message_size, const char *message,
+ Oid relid, DeparsedCommandType cmdtype)
+{
+ MemoryContext oldcontext;
+ ReorderBufferChange *change;
+
+ Assert(TransactionIdIsValid(xid));
+
+ oldcontext = MemoryContextSwitchTo(rb->context);
+
+ change = ReorderBufferGetChange(rb);
+ change->action = REORDER_BUFFER_CHANGE_DDL;
+ change->data.ddl.prefix = pstrdup(prefix);
+ change->data.ddl.relid = relid;
+ change->data.ddl.cmdtype = cmdtype;
+ change->data.ddl.message_size = message_size;
+ change->data.ddl.message = palloc(message_size);
+ memcpy(change->data.ddl.message, message, message_size);
+
+ ReorderBufferQueueChange(rb, xid, lsn, change, false);
+
+ MemoryContextSwitchTo(oldcontext);
+}
+
/*
* AssertTXNLsnOrder
* Verify LSN ordering of transaction lists in the reorderbuffer
@@ -1997,6 +2036,29 @@ ReorderBufferApplyMessage(ReorderBuffer *rb, ReorderBufferTXN *txn,
change->data.msg.message);
}
+/*
+ * Helper function for ReorderBufferProcessTXN for applying the DDL message.
+ */
+static inline void
+ReorderBufferApplyDDLMessage(ReorderBuffer *rb, ReorderBufferTXN *txn,
+ ReorderBufferChange *change, bool streaming)
+{
+ if (streaming)
+ rb->stream_ddl(rb, txn, change->lsn,
+ change->data.ddl.prefix,
+ change->data.ddl.relid,
+ change->data.ddl.cmdtype,
+ change->data.ddl.message_size,
+ change->data.ddl.message);
+ else
+ rb->ddl(rb, txn, change->lsn,
+ change->data.ddl.prefix,
+ change->data.ddl.relid,
+ change->data.ddl.cmdtype,
+ change->data.ddl.message_size,
+ change->data.ddl.message);
+}
+
/*
* Function to store the command id and snapshot at the end of the current
* stream so that we can reuse the same while sending the next stream.
@@ -2379,6 +2441,10 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
ReorderBufferApplyMessage(rb, txn, change, streaming);
break;
+ case REORDER_BUFFER_CHANGE_DDL:
+ ReorderBufferApplyDDLMessage(rb, txn, change, streaming);
+ break;
+
case REORDER_BUFFER_CHANGE_INVALIDATION:
/* Execute the invalidation messages locally */
ReorderBufferExecuteInvalidations(change->data.inval.ninvalidations,
@@ -3835,6 +3901,39 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
change->data.msg.message_size);
data += change->data.msg.message_size;
+ break;
+ }
+ case REORDER_BUFFER_CHANGE_DDL:
+ {
+ char *data;
+ Size prefix_size = strlen(change->data.ddl.prefix) + 1;
+
+ sz += prefix_size + change->data.ddl.message_size +
+ sizeof(Size) + sizeof(Oid) + sizeof(DeparsedCommandType) + sizeof(Size);
+ ReorderBufferSerializeReserve(rb, sz);
+
+ data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
+
+ /* might have been reallocated above */
+ ondisk = (ReorderBufferDiskChange *) rb->outbuf;
+
+ /* write the prefix, relid and cmdtype including the size */
+ memcpy(data, &prefix_size, sizeof(Size));
+ data += sizeof(Size);
+ memcpy(data, &change->data.ddl.relid, sizeof(Oid));
+ data += sizeof(Oid);
+ memcpy(data, &change->data.ddl.cmdtype, sizeof(DeparsedCommandType));
+ data += sizeof(DeparsedCommandType);
+ memcpy(data, change->data.ddl.prefix, prefix_size);
+ data += prefix_size;
+
+ /* write the message including the size */
+ memcpy(data, &change->data.ddl.message_size, sizeof(Size));
+ data += sizeof(Size);
+ memcpy(data, change->data.ddl.message,
+ change->data.ddl.message_size);
+ data += change->data.ddl.message_size;
+
break;
}
case REORDER_BUFFER_CHANGE_INVALIDATION:
@@ -4149,6 +4248,15 @@ ReorderBufferChangeSize(ReorderBufferChange *change)
sz += prefix_size + change->data.msg.message_size +
sizeof(Size) + sizeof(Size);
+ break;
+ }
+ case REORDER_BUFFER_CHANGE_DDL:
+ {
+ Size prefix_size = strlen(change->data.ddl.prefix) + 1;
+
+ sz += prefix_size + change->data.ddl.message_size +
+ sizeof(Size) + sizeof(Size) + sizeof(Oid) + sizeof(DeparsedCommandType);
+
break;
}
case REORDER_BUFFER_CHANGE_INVALIDATION:
@@ -4426,6 +4534,33 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
change->data.msg.message_size);
data += change->data.msg.message_size;
+ break;
+ }
+ case REORDER_BUFFER_CHANGE_DDL:
+ {
+ Size prefix_size;
+
+ /* read prefix */
+ memcpy(&prefix_size, data, sizeof(Size));
+ data += sizeof(Size);
+ memcpy(&change->data.ddl.relid, data, sizeof(Oid));
+ data += sizeof(Oid);
+ memcpy(&change->data.ddl.cmdtype, data, sizeof(DeparsedCommandType));
+ data += sizeof(DeparsedCommandType);
+ change->data.ddl.prefix = MemoryContextAlloc(rb->context, prefix_size);
+ memcpy(change->data.ddl.prefix, data, prefix_size);
+ Assert(change->data.ddl.prefix[prefix_size - 1] == '\0');
+ data += prefix_size;
+
+ /* read the message */
+ memcpy(&change->data.msg.message_size, data, sizeof(Size));
+ data += sizeof(Size);
+ change->data.msg.message = MemoryContextAlloc(rb->context,
+ change->data.msg.message_size);
+ memcpy(change->data.msg.message, data,
+ change->data.msg.message_size);
+ data += change->data.msg.message_size;
+
break;
}
case REORDER_BUFFER_CHANGE_INVALIDATION:
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 0ee764d68f..5d2cdce5ec 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -166,6 +166,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
@@ -191,7 +192,10 @@
#include "storage/lmgr.h"
#include "storage/proc.h"
#include "storage/procarray.h"
+#include "tcop/ddldeparse.h"
+#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
+#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
@@ -3277,6 +3281,189 @@ apply_handle_truncate(StringInfo s)
end_replication_step();
}
+/*
+ * Handle CREATE TABLE command
+ *
+ * Call AddSubscriptionRelState for CREATE TABLE command to set the relstate to
+ * SUBREL_STATE_READY so DML changes on this new table can be replicated without
+ * having to manually run "ALTER SUBSCRIPTION ... REFRESH PUBLICATION"
+ */
+static void
+postprocess_ddl_create_table(RawStmt *command)
+{
+ CommandTag commandTag;
+ RangeVar *rv = NULL;
+ Oid relid;
+ Oid relnamespace_oid = InvalidOid;
+ CreateStmt *cstmt;
+ char *schemaname = NULL;
+ char *relname = NULL;
+
+ commandTag = CreateCommandTag((Node *) command);
+
+ if (commandTag != CMDTAG_CREATE_TABLE)
+ return;
+
+ cstmt = (CreateStmt *) command->stmt;
+ rv = cstmt->relation;
+ if (!rv)
+ return;
+
+ schemaname = rv->schemaname;
+ relname = rv->relname;
+
+ if (schemaname != NULL)
+ relnamespace_oid = get_namespace_oid(schemaname, false);
+
+ if (OidIsValid(relnamespace_oid))
+ relid = get_relname_relid(relname, relnamespace_oid);
+ else
+ relid = RelnameGetRelid(relname);
+
+ if (OidIsValid(relid))
+ {
+ AddSubscriptionRelState(MySubscription->oid, relid,
+ SUBREL_STATE_READY,
+ InvalidXLogRecPtr);
+ ereport(DEBUG1,
+ (errmsg_internal("table \"%s\" added to subscription \"%s\"",
+ relname, MySubscription->name)));
+ }
+}
+
+/*
+ * Handle DDL commands
+ *
+ * Handle DDL replication messages. Convert the json string into a query
+ * string and run it through the query portal.
+ */
+static void
+apply_handle_ddl(StringInfo s)
+{
+ XLogRecPtr lsn;
+ const char *prefix = NULL;
+ char *message = NULL;
+ char *ddl_command;
+ Size sz;
+ List *parsetree_list;
+ ListCell *parsetree_item;
+ DestReceiver *receiver;
+ MemoryContext oldcontext;
+ const char *save_debug_query_string = debug_query_string;
+
+ message = logicalrep_read_ddl(s, &lsn, &prefix, &sz);
+
+ SetCurrentStatementStartTimestamp();
+
+ if (!IsTransactionState())
+ {
+ StartTransactionCommand();
+ maybe_reread_subscription();
+ }
+
+ MemoryContextSwitchTo(ApplyMessageContext);
+
+ ddl_command = deparse_ddl_json_to_string(message);
+ debug_query_string = ddl_command;
+
+ /* DestNone for logical replication */
+ receiver = CreateDestReceiver(DestNone);
+ parsetree_list = pg_parse_query(ddl_command);
+
+ foreach(parsetree_item, parsetree_list)
+ {
+ List *plantree_list;
+ List *querytree_list;
+ RawStmt *command = (RawStmt *) lfirst(parsetree_item);
+ CommandTag commandTag;
+ MemoryContext per_parsetree_context = NULL;
+ Portal portal;
+ bool snapshot_set = false;
+
+ commandTag = CreateCommandTag((Node *) command);
+
+ /* If we got a cancel signal in parsing or prior command, quit */
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * Set up a snapshot if parse analysis/planning will need one.
+ */
+ if (analyze_requires_snapshot(command))
+ {
+ PushActiveSnapshot(GetTransactionSnapshot());
+ snapshot_set = true;
+ }
+
+ /*
+ * We do the work for each parsetree in a short-lived context, to
+ * limit the memory used when there are many commands in the string.
+ */
+ per_parsetree_context =
+ AllocSetContextCreate(CurrentMemoryContext,
+ "execute_sql_string per-statement context",
+ ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(per_parsetree_context);
+
+ querytree_list = pg_analyze_and_rewrite_fixedparams(command,
+ ddl_command,
+ NULL, 0, NULL);
+
+ plantree_list = pg_plan_queries(querytree_list, ddl_command, 0, NULL);
+
+ /* Done with the snapshot used for parsing/planning */
+ if (snapshot_set)
+ PopActiveSnapshot();
+
+ portal = CreatePortal("logical replication", true, true);
+
+ /*
+ * We don't have to copy anything into the portal, because everything
+ * we are passing here is in ApplyMessageContext or the
+ * per_parsetree_context, and so will outlive the portal anyway.
+ */
+ PortalDefineQuery(portal,
+ NULL,
+ ddl_command,
+ commandTag,
+ plantree_list,
+ NULL);
+
+ /*
+ * Start the portal. No parameters here.
+ */
+ PortalStart(portal, NULL, 0, InvalidSnapshot);
+
+ /*
+ * Switch back to transaction context for execution.
+ */
+ MemoryContextSwitchTo(oldcontext);
+
+ (void) PortalRun(portal,
+ FETCH_ALL,
+ true,
+ true,
+ receiver,
+ receiver,
+ NULL);
+
+ PortalDrop(portal, false);
+
+ CommandCounterIncrement();
+
+ /*
+ * Table created by DDL replication (database level) is automatically
+ * added to the subscription here.
+ */
+ postprocess_ddl_create_table(command);
+
+ /* Now we may drop the per-parsetree context, if one was created. */
+ MemoryContextDelete(per_parsetree_context);
+ }
+
+ debug_query_string = save_debug_query_string;
+
+ CommandCounterIncrement();
+}
/*
* Logical replication protocol message dispatcher.
@@ -3342,6 +3529,10 @@ apply_dispatch(StringInfo s)
*/
break;
+ case LOGICAL_REP_MSG_DDL:
+ apply_handle_ddl(s);
+ break;
+
case LOGICAL_REP_MSG_STREAM_START:
apply_handle_stream_start(s);
break;
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index b08ca55041..af634b25bc 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -18,6 +18,7 @@
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_subscription.h"
#include "commands/defrem.h"
+#include "commands/publicationcmds.h"
#include "commands/subscriptioncmds.h"
#include "executor/executor.h"
#include "fmgr.h"
@@ -55,6 +56,11 @@ static void pgoutput_message(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn, XLogRecPtr message_lsn,
bool transactional, const char *prefix,
Size sz, const char *message);
+static void pgoutput_ddl(LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn, XLogRecPtr message_lsn,
+ const char *prefix, Oid relid,
+ DeparsedCommandType cmdtype,
+ Size sz, const char *message);
static bool pgoutput_origin_filter(LogicalDecodingContext *ctx,
RepOriginId origin_id);
static void pgoutput_begin_prepare_txn(LogicalDecodingContext *ctx,
@@ -207,6 +213,13 @@ typedef struct RelationSyncEntry
typedef struct PGOutputTxnData
{
bool sent_begin_txn; /* flag indicating whether BEGIN has been sent */
+ /*
+ * Maintain list of deleted oids which are added at command start.
+ * This is required because at command end when the actual command is sent
+ * the catalogs have been modified and it is not possible to know if the oid
+ * was referring to an object that was part of the publication.
+ */
+ List *deleted_relids; /* maintain list of deleted table oids */
} PGOutputTxnData;
/* Map used to remember which relation schemas we sent. */
@@ -254,6 +267,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
cb->change_cb = pgoutput_change;
cb->truncate_cb = pgoutput_truncate;
cb->message_cb = pgoutput_message;
+ cb->ddl_cb = pgoutput_ddl;
cb->commit_cb = pgoutput_commit_txn;
cb->begin_prepare_cb = pgoutput_begin_prepare_txn;
@@ -270,6 +284,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
cb->stream_commit_cb = pgoutput_stream_commit;
cb->stream_change_cb = pgoutput_change;
cb->stream_message_cb = pgoutput_message;
+ cb->stream_ddl_cb = pgoutput_ddl;
cb->stream_truncate_cb = pgoutput_truncate;
/* transaction streaming - two-phase commit */
cb->stream_prepare_cb = pgoutput_stream_prepare_txn;
@@ -505,6 +520,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
/* Init publication state. */
data->publications = NIL;
+ data->deleted_relids = NIL;
publications_valid = false;
/*
@@ -533,6 +549,34 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
}
}
+/* Initialize the per-transaction private data for the given transaction. */
+static void
+init_txn_data(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
+{
+ PGOutputTxnData *txndata;
+
+ if (txn->output_plugin_private != NULL)
+ return;
+
+ txndata = MemoryContextAllocZero(ctx->context, sizeof(PGOutputTxnData));
+
+ txn->output_plugin_private = txndata;
+}
+
+/* Clean up the per-transaction private data for the given transaction. */
+static void
+clean_txn_data(ReorderBufferTXN *txn)
+{
+ PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
+
+ if (txndata == NULL)
+ return;
+
+ list_free(txndata->deleted_relids);
+ pfree(txndata);
+ txn->output_plugin_private = NULL;
+}
+
/*
* BEGIN callback.
*
@@ -546,10 +590,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
static void
pgoutput_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
{
- PGOutputTxnData *txndata = MemoryContextAllocZero(ctx->context,
- sizeof(PGOutputTxnData));
-
- txn->output_plugin_private = txndata;
+ init_txn_data(ctx, txn);
}
/*
@@ -594,8 +635,7 @@ pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
*/
sent_begin_txn = txndata->sent_begin_txn;
OutputPluginUpdateProgress(ctx, !sent_begin_txn);
- pfree(txndata);
- txn->output_plugin_private = NULL;
+ clean_txn_data(txn);
if (!sent_begin_txn)
{
@@ -637,6 +677,8 @@ pgoutput_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
OutputPluginPrepareWrite(ctx, true);
logicalrep_write_prepare(ctx->out, txn, prepare_lsn);
OutputPluginWrite(ctx, true);
+
+ clean_txn_data(txn);
}
/*
@@ -1665,6 +1707,107 @@ pgoutput_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
OutputPluginWrite(ctx, true);
}
+/* Check if the given object is published. */
+static bool
+is_object_published(LogicalDecodingContext *ctx, Oid objid)
+{
+ Relation relation = NULL;
+ RelationSyncEntry *relentry;
+ PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
+
+ /* First check the DDL command filter. */
+ switch (get_rel_relkind(objid))
+ {
+ case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
+ relation = RelationIdGetRelation(objid);
+ relentry = get_rel_sync_entry(data, relation);
+ RelationClose(relation);
+
+ /* Only send this ddl if we publish ddl message. */
+ if (relentry->pubactions.pubddl_table)
+ return true;
+
+ break;
+ default:
+ /* unsupported objects */
+ return false;
+ }
+
+ return false;
+}
+
+/*
+ * Send the decoded DDL message.
+ */
+static void
+pgoutput_ddl(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ Size sz, const char *message)
+{
+ PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
+
+ /*
+ * Check if the given object is published. Note that for dropped objects,
+ * we cannot get the required information from the catalog, so we skip the
+ * check for them.
+ */
+ if (cmdtype != DCT_TableDropEnd && !is_object_published(ctx, relid))
+ return;
+
+ switch (cmdtype)
+ {
+ case DCT_TableDropStart:
+ {
+ MemoryContext oldctx;
+
+ init_txn_data(ctx, txn);
+
+ txndata = (PGOutputTxnData *) txn->output_plugin_private;
+
+ /*
+ * On DROP start, add the relid to a deleted_relid list if the
+ * relid is part of a publication that supports ddl
+ * publication. We need this because on DROP end, the relid
+ * will no longer be valid. Later on Drop end, verify that the
+ * drop is for a relid that is on the deleted_rid list, and
+ * only then send the ddl message.
+ */
+ oldctx = MemoryContextSwitchTo(ctx->context);
+ txndata->deleted_relids = lappend_oid(txndata->deleted_relids,
+ relid);
+ MemoryContextSwitchTo(oldctx);
+ }
+ return;
+
+ case DCT_TableDropEnd:
+ if (!list_member_oid(txndata->deleted_relids, relid))
+ return;
+
+ txndata->deleted_relids = list_delete_oid(txndata->deleted_relids,
+ relid);
+ break;
+
+ case DCT_TableAlter:
+ case DCT_SimpleCmd:
+ /* do nothing */
+ break;
+
+ default:
+ elog(ERROR, "unsupported type %d", cmdtype);
+ break;
+ }
+
+ /* Send BEGIN if we haven't yet */
+ if (txndata && !txndata->sent_begin_txn)
+ pgoutput_send_begin(ctx, txn);
+
+ OutputPluginPrepareWrite(ctx, true);
+ logicalrep_write_ddl(ctx->out, message_lsn, prefix, sz, message);
+ OutputPluginWrite(ctx, true);
+}
+
/*
* Return true if the data is associated with an origin and the user has
* requested the changes that don't have an origin, false otherwise.
@@ -1813,6 +1956,7 @@ pgoutput_stream_abort(struct LogicalDecodingContext *ctx,
OutputPluginWrite(ctx, true);
cleanup_rel_sync_cache(toptxn->xid, false);
+ clean_txn_data(txn);
}
/*
@@ -1838,6 +1982,7 @@ pgoutput_stream_commit(struct LogicalDecodingContext *ctx,
OutputPluginWrite(ctx, true);
cleanup_rel_sync_cache(txn->xid, true);
+ clean_txn_data(txn);
}
/*
@@ -1856,6 +2001,8 @@ pgoutput_stream_prepare_txn(LogicalDecodingContext *ctx,
OutputPluginPrepareWrite(ctx, true);
logicalrep_write_stream_prepare(ctx->out, txn, prepare_lsn);
OutputPluginWrite(ctx, true);
+
+ clean_txn_data(txn);
}
/*
@@ -1972,8 +2119,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->replicate_valid = false;
entry->schema_sent = false;
entry->streamed_txns = NIL;
- entry->pubactions.pubinsert = entry->pubactions.pubupdate =
- entry->pubactions.pubdelete = entry->pubactions.pubtruncate = false;
+ memset(&entry->pubactions, 0, sizeof(entry->pubactions));
entry->new_slot = NULL;
entry->old_slot = NULL;
memset(entry->exprstate, 0, sizeof(entry->exprstate));
@@ -2027,10 +2173,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
entry->streamed_txns = NIL;
bms_free(entry->columns);
entry->columns = NULL;
- entry->pubactions.pubinsert = false;
- entry->pubactions.pubupdate = false;
- entry->pubactions.pubdelete = false;
- entry->pubactions.pubtruncate = false;
+ memset(&entry->pubactions, 0, sizeof(entry->pubactions));
/*
* Tuple slots cleanups. (Will be rebuilt later if needed).
@@ -2129,53 +2272,61 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
publish = true;
}
- /*
- * If the relation is to be published, determine actions to
- * publish, and list of columns, if appropriate.
- *
- * Don't publish changes for partitioned tables, because
- * publishing those of its partitions suffices, unless partition
- * changes won't be published due to pubviaroot being set.
- */
- if (publish &&
- (relkind != RELKIND_PARTITIONED_TABLE || pub->pubviaroot))
+ if (publish)
{
- entry->pubactions.pubinsert |= pub->pubactions.pubinsert;
- entry->pubactions.pubupdate |= pub->pubactions.pubupdate;
- entry->pubactions.pubdelete |= pub->pubactions.pubdelete;
- entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate;
-
/*
- * We want to publish the changes as the top-most ancestor
- * across all publications. So we need to check if the already
- * calculated level is higher than the new one. If yes, we can
- * ignore the new value (as it's a child). Otherwise the new
- * value is an ancestor, so we keep it.
+ * The behavior of DDL logical replication is unrelated to
+ * pubviaroot.
*/
- if (publish_ancestor_level > ancestor_level)
- continue;
+ entry->pubactions.pubddl_table |= pub->pubactions.pubddl_table;
/*
- * If we found an ancestor higher up in the tree, discard the
- * list of publications through which we replicate it, and use
- * the new ancestor.
+ * If the relation is to be published, determine actions to
+ * publish, and list of columns, if appropriate.
+ *
+ * Don't publish changes for partitioned tables, because
+ * publishing those of its partitions suffices, unless partition
+ * changes won't be published due to pubviaroot being set.
*/
- if (publish_ancestor_level < ancestor_level)
+ if (relkind != RELKIND_PARTITIONED_TABLE || pub->pubviaroot)
{
- publish_as_relid = pub_relid;
- publish_ancestor_level = ancestor_level;
+ entry->pubactions.pubinsert |= pub->pubactions.pubinsert;
+ entry->pubactions.pubupdate |= pub->pubactions.pubupdate;
+ entry->pubactions.pubdelete |= pub->pubactions.pubdelete;
+ entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate;
- /* reset the publication list for this relation */
- rel_publications = NIL;
- }
- else
- {
- /* Same ancestor level, has to be the same OID. */
- Assert(publish_as_relid == pub_relid);
- }
+ /*
+ * We want to publish the changes as the top-most ancestor
+ * across all publications. So we need to check if the already
+ * calculated level is higher than the new one. If yes, we can
+ * ignore the new value (as it's a child). Otherwise the new
+ * value is an ancestor, so we keep it.
+ */
+ if (publish_ancestor_level > ancestor_level)
+ continue;
+
+ /*
+ * If we found an ancestor higher up in the tree, discard the
+ * list of publications through which we replicate it, and use
+ * the new ancestor.
+ */
+ if (publish_ancestor_level < ancestor_level)
+ {
+ publish_as_relid = pub_relid;
+ publish_ancestor_level = ancestor_level;
+
+ /* reset the publication list for this relation */
+ rel_publications = NIL;
+ }
+ else
+ {
+ /* Same ancestor level, has to be the same OID. */
+ Assert(publish_as_relid == pub_relid);
+ }
- /* Track publications for this ancestor. */
- rel_publications = lappend(rel_publications, pub);
+ /* Track publications for this ancestor. */
+ rel_publications = lappend(rel_publications, pub);
+ }
}
}
diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c
index 4bd713a0b4..a31b061eb7 100644
--- a/src/backend/tcop/cmdtag.c
+++ b/src/backend/tcop/cmdtag.c
@@ -26,10 +26,11 @@ typedef struct CommandTagBehavior
const bool table_rewrite_ok;
const bool display_rowcount; /* should the number of rows affected be
* shown in the command completion string */
+ const bool ddl_replication_ok;
} CommandTagBehavior;
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
- { name, (uint8) (sizeof(name) - 1), evtrgok, rwrok, rowcnt },
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, ddl_repl_ok) \
+ { name, (uint8) (sizeof(name) - 1), evtrgok, rwrok, rowcnt, ddl_repl_ok },
static const CommandTagBehavior tag_behavior[COMMAND_TAG_NEXTTAG] = {
#include "tcop/cmdtaglist.h"
@@ -57,6 +58,21 @@ GetCommandTagNameAndLen(CommandTag commandTag, Size *len)
return tag_behavior[commandTag].name;
}
+CommandTag *
+GetCommandTagsForDDLRepl(int *ncommands)
+{
+ CommandTag *ddlrepl_commands = palloc0(COMMAND_TAG_NEXTTAG * sizeof(CommandTag));
+ *ncommands = 0;
+
+ for(int i = 0; i < COMMAND_TAG_NEXTTAG; i++)
+ {
+ if (tag_behavior[i].ddl_replication_ok)
+ ddlrepl_commands[(*ncommands)++] = (CommandTag) i;
+ }
+
+ return ddlrepl_commands;
+}
+
bool
command_tag_display_rowcount(CommandTag commandTag)
{
@@ -75,6 +91,12 @@ command_tag_table_rewrite_ok(CommandTag commandTag)
return tag_behavior[commandTag].table_rewrite_ok;
}
+bool
+command_tag_ddl_replication_ok(CommandTag commandTag)
+{
+ return tag_behavior[commandTag].ddl_replication_ok;
+}
+
/*
* Search CommandTag by name
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c0f7f29747..7667b9e34b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -895,7 +895,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_CreateEventTrigStmt:
/* no event triggers on event triggers */
- CreateEventTrigger((CreateEventTrigStmt *) parsetree);
+ CreateEventTrigger((CreateEventTrigStmt *) parsetree, false);
break;
case T_AlterEventTrigStmt:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8a08463c2b..f3022f08c1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5720,6 +5720,7 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
pubdesc->pubactions.pubupdate |= pubform->pubupdate;
pubdesc->pubactions.pubdelete |= pubform->pubdelete;
pubdesc->pubactions.pubtruncate |= pubform->pubtruncate;
+ pubdesc->pubactions.pubddl_table |= pubform->pubddl_table;
/*
* Check if all columns referenced in the filter expression are part
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5dab1ba9ea..334726cd0d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -51,6 +51,7 @@
#include "catalog/pg_largeobject_d.h"
#include "catalog/pg_largeobject_metadata_d.h"
#include "catalog/pg_proc_d.h"
+#include "catalog/pg_publication.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_trigger_d.h"
#include "catalog/pg_type_d.h"
@@ -4047,6 +4048,7 @@ getPublications(Archive *fout, int *numPublications)
int i_pubupdate;
int i_pubdelete;
int i_pubtruncate;
+ int i_pubddl_table;
int i_pubviaroot;
int i,
ntups;
@@ -4062,23 +4064,29 @@ getPublications(Archive *fout, int *numPublications)
resetPQExpBuffer(query);
/* Get the publications. */
- if (fout->remoteVersion >= 130000)
+ if (fout->remoteVersion >= 160000)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubddl_table, p.pubviaroot "
+ "FROM pg_publication p");
+ else if (fout->remoteVersion >= 130000)
+ appendPQExpBufferStr(query,
+ "SELECT p.tableoid, p.oid, p.pubname, "
+ "p.pubowner, "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, p.pubviaroot "
"FROM pg_publication p");
else if (fout->remoteVersion >= 110000)
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false as p.pubddl_table, false AS pubviaroot "
"FROM pg_publication p");
else
appendPQExpBufferStr(query,
"SELECT p.tableoid, p.oid, p.pubname, "
"p.pubowner, "
- "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubviaroot "
+ "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false as p.pubddl_table, false AS pubviaroot "
"FROM pg_publication p");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -4094,6 +4102,7 @@ getPublications(Archive *fout, int *numPublications)
i_pubupdate = PQfnumber(res, "pubupdate");
i_pubdelete = PQfnumber(res, "pubdelete");
i_pubtruncate = PQfnumber(res, "pubtruncate");
+ i_pubddl_table = PQfnumber(res, "pubddl_table");
i_pubviaroot = PQfnumber(res, "pubviaroot");
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
@@ -4117,6 +4126,8 @@ getPublications(Archive *fout, int *numPublications)
(strcmp(PQgetvalue(res, i, i_pubdelete), "t") == 0);
pubinfo[i].pubtruncate =
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
+ pubinfo[i].pubddl_table =
+ (strcmp(PQgetvalue(res, i, i_pubddl_table), "t") == 0);
pubinfo[i].pubviaroot =
(strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
@@ -4196,7 +4207,10 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
first = false;
}
- appendPQExpBufferChar(query, '\'');
+ appendPQExpBufferStr(query, "'");
+
+ if (pubinfo->pubddl_table)
+ appendPQExpBufferStr(query, ", ddl = 'table'");
if (pubinfo->pubviaroot)
appendPQExpBufferStr(query, ", publish_via_partition_root = true");
@@ -7992,6 +8006,7 @@ getEventTriggers(Archive *fout, int *numEventTriggers)
query = createPQExpBuffer();
+ /*Skip internally created event triggers by checking evtisinternal */
appendPQExpBufferStr(query,
"SELECT e.tableoid, e.oid, evtname, evtenabled, "
"evtevent, evtowner, "
@@ -8000,6 +8015,7 @@ getEventTriggers(Archive *fout, int *numEventTriggers)
" from unnest(evttags) as t(x)), ', ') as evttags, "
"e.evtfoid::regproc as evtfname "
"FROM pg_event_trigger e "
+ "WHERE NOT e.evtisinternal "
"ORDER BY e.oid");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index bc8f2ec36d..7673d153aa 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -621,6 +621,7 @@ typedef struct _PublicationInfo
bool pubupdate;
bool pubdelete;
bool pubtruncate;
+ bool pubddl_table;
bool pubviaroot;
} PublicationInfo;
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 15852188c4..5acbfaeb8b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2912,7 +2912,7 @@ my %tests = (
create_order => 50,
create_sql => 'CREATE PUBLICATION pub2
FOR ALL TABLES
- WITH (publish = \'\');',
+ WITH (publish = \'\', ddl = \'\');',
regexp => qr/^
\QCREATE PUBLICATION pub2 FOR ALL TABLES WITH (publish = '');\E
/xm,
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..4df5660b51 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,6 +10,7 @@
/gistdesc.c
/hashdesc.c
/heapdesc.c
+/logicalddlmsgdesc.c
/logicalmsgdesc.c
/mxactdesc.c
/nbtdesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..daf1730252 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -26,6 +26,7 @@
#include "commands/dbcommands_xlog.h"
#include "commands/sequence.h"
#include "commands/tablespace.h"
+#include "replication/ddlmessage.h"
#include "replication/message.h"
#include "replication/origin.h"
#include "rmgrdesc.h"
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 9325a46b8f..750ea19dc1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6180,7 +6180,7 @@ listPublications(const char *pattern)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
- static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
+ static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false};
if (pset.sversion < 100000)
{
@@ -6207,6 +6207,10 @@ listPublications(const char *pattern)
gettext_noop("Inserts"),
gettext_noop("Updates"),
gettext_noop("Deletes"));
+ if (pset.sversion >= 160000)
+ appendPQExpBuffer(&buf,
+ ",\n pubddl_table AS \"%s\"",
+ gettext_noop("Table DDLs"));
if (pset.sversion >= 110000)
appendPQExpBuffer(&buf,
",\n pubtruncate AS \"%s\"",
@@ -6305,6 +6309,7 @@ describePublications(const char *pattern)
PGresult *res;
bool has_pubtruncate;
bool has_pubviaroot;
+ bool has_pubddl;
PQExpBufferData title;
printTableContent cont;
@@ -6319,6 +6324,7 @@ describePublications(const char *pattern)
return true;
}
+ has_pubddl = (pset.sversion >= 160000);
has_pubtruncate = (pset.sversion >= 110000);
has_pubviaroot = (pset.sversion >= 130000);
@@ -6328,6 +6334,9 @@ describePublications(const char *pattern)
"SELECT oid, pubname,\n"
" pg_catalog.pg_get_userbyid(pubowner) AS owner,\n"
" puballtables, pubinsert, pubupdate, pubdelete");
+ if (has_pubddl)
+ appendPQExpBufferStr(&buf,
+ ", pubddl_table");
if (has_pubtruncate)
appendPQExpBufferStr(&buf,
", pubtruncate");
@@ -6381,6 +6390,8 @@ describePublications(const char *pattern)
bool puballtables = strcmp(PQgetvalue(res, i, 3), "t") == 0;
printTableOpt myopt = pset.popt.topt;
+ if (has_pubddl)
+ ncols++;
if (has_pubtruncate)
ncols++;
if (has_pubviaroot)
@@ -6395,6 +6406,8 @@ describePublications(const char *pattern)
printTableAddHeader(&cont, gettext_noop("Inserts"), true, align);
printTableAddHeader(&cont, gettext_noop("Updates"), true, align);
printTableAddHeader(&cont, gettext_noop("Deletes"), true, align);
+ if (has_pubddl)
+ printTableAddHeader(&cont, gettext_noop("Table DDLs"), true, align);
if (has_pubtruncate)
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
if (has_pubviaroot)
@@ -6405,10 +6418,12 @@ describePublications(const char *pattern)
printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false);
printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false);
- if (has_pubtruncate)
+ if (has_pubddl)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
- if (has_pubviaroot)
+ if (has_pubtruncate)
printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ if (has_pubviaroot)
+ printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
if (!puballtables)
{
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 463bcb67c5..abcbe97593 100644
--- a/src/include/access/rmgrlist.h
+++ b/src/include/access/rmgrlist.h
@@ -47,3 +47,4 @@ PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_i
PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL, NULL, NULL)
PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL, generic_mask, NULL)
PG_RMGR(RM_LOGICALMSG_ID, "LogicalMessage", logicalmsg_redo, logicalmsg_desc, logicalmsg_identify, NULL, NULL, NULL, logicalmsg_decode)
+PG_RMGR(RM_LOGICALDDLMSG_ID, "LogicalDDLMessage", logicalddlmsg_redo, logicalddlmsg_desc, logicalddlmsg_identify, NULL, NULL, NULL, logicalddl_decode)
diff --git a/src/include/catalog/pg_event_trigger.h b/src/include/catalog/pg_event_trigger.h
index e30550f6e1..f8f00e88fa 100644
--- a/src/include/catalog/pg_event_trigger.h
+++ b/src/include/catalog/pg_event_trigger.h
@@ -36,6 +36,7 @@ CATALOG(pg_event_trigger,3466,EventTriggerRelationId)
* called */
char evtenabled; /* trigger's firing configuration WRT
* session_replication_role */
+ bool evtisinternal; /* trigger is system-generated */
#ifdef CATALOG_VARLEN
text evttags[1]; /* command TAGs this event trigger targets */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 76ff23b779..014116ff9e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11145,6 +11145,14 @@
proname => 'pg_logical_emit_message', provolatile => 'v', proparallel => 'u',
prorettype => 'pg_lsn', proargtypes => 'bool text bytea',
prosrc => 'pg_logical_emit_message_bytea' },
+{ oid => '3813', descr => 'emit a textual logical decoding message',
+ proname => 'pg_logical_emit_ddl_message', provolatile => 'v', proparallel => 'u',
+ prorettype => 'pg_lsn', proargtypes => 'bool text text',
+ prosrc => 'pg_logical_emit_ddl_message_text' },
+{ oid => '3814', descr => 'emit a binary logical decoding message',
+ proname => 'pg_logical_emit_ddl_message', provolatile => 'v', proparallel => 'u',
+ prorettype => 'pg_lsn', proargtypes => 'text regclass int4 text',
+ prosrc => 'pg_logical_emit_ddl_message_bytea' },
# event triggers
{ oid => '3566', descr => 'list objects dropped by the current command',
@@ -12049,5 +12057,17 @@
{ oid => '4643', descr => 'expand JSON format DDL to a plain text DDL command',
proname => 'ddl_deparse_expand_command', prorettype => 'text',
proargtypes => 'text', prosrc => 'ddl_deparse_expand_command' },
+{ oid => '4644', descr => 'trigger for ddl command deparse end',
+ proname => 'publication_deparse_ddl_command_end', prorettype => 'event_trigger',
+ proargtypes => '', prosrc => 'publication_deparse_ddl_command_end' },
+{ oid => '4645', descr => 'trigger for ddl command deparse start',
+ proname => 'publication_deparse_ddl_command_start', prorettype => 'event_trigger',
+ proargtypes => '', prosrc => 'publication_deparse_ddl_command_start' },
+{ oid => '4646', descr => 'trigger for ddl command deparse table rewrite',
+ proname => 'publication_deparse_table_rewrite', prorettype => 'event_trigger',
+ proargtypes => '', prosrc => 'publication_deparse_table_rewrite' },
+{ oid => '4647', descr => 'trigger for ddl command deparse table init',
+ proname => 'publication_deparse_table_init_write', prorettype => 'event_trigger',
+ proargtypes => '', prosrc => 'publication_deparse_table_init_write' },
]
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 6ecaa2a01e..303f5412d8 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -18,8 +18,16 @@
#define PG_PUBLICATION_H
#include "catalog/genbki.h"
-#include "catalog/objectaddress.h"
#include "catalog/pg_publication_d.h"
+#include "nodes/pg_list.h"
+
+/* Publication trigger events */
+#define PUB_TRIG_DDL_CMD_START "ddl_command_start"
+#define PUB_TRIG_DDL_CMD_END "ddl_command_end"
+#define PUB_TRIG_TBL_REWRITE "table_rewrite"
+
+/* Publication event trigger prefix */
+#define PUB_EVENT_TRIG_FORMAT "pg_deparse_trig_%s_%u"
/* ----------------
* pg_publication definition. cpp turns this into
@@ -54,6 +62,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 +83,7 @@ typedef struct PublicationActions
bool pubupdate;
bool pubdelete;
bool pubtruncate;
+ bool pubddl_table;
} PublicationActions;
typedef struct PublicationDesc
@@ -103,13 +115,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 +150,6 @@ extern List *GetPubPartitionOptionRelations(List *result,
extern Oid GetTopMostAncestorInPublication(Oid puboid, List *ancestors,
int *ancestor_level);
-extern bool is_publishable_relation(Relation rel);
extern bool is_schema_publication(Oid pubid);
-extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
- bool if_not_exists);
-extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
- bool if_not_exists);
-
-extern Bitmapset *pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols,
- MemoryContext mcxt);
#endif /* PG_PUBLICATION_H */
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 28b3486b9e..3927031a2e 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -73,6 +73,12 @@ typedef struct SQLDropObject
#define AT_REWRITE_COLUMN_REWRITE 0x04
#define AT_REWRITE_ACCESS_METHOD 0x08
+/* Trigger events */
+#define TRIG_DDL_CMD_START "ddl_command_start"
+#define TRIG_DDL_CMD_END "ddl_command_end"
+#define TRIG_TBL_REWRITE "table_rewrite"
+#define TRIG_TBL_CMD_DROP "sql_drop"
+
/*
* EventTriggerData is the node type that is passed as fmgr "context" info
* when a function is called by the event trigger manager.
@@ -80,7 +86,7 @@ typedef struct SQLDropObject
#define CALLED_AS_EVENT_TRIGGER(fcinfo) \
((fcinfo)->context != NULL && IsA((fcinfo)->context, EventTriggerData))
-extern Oid CreateEventTrigger(CreateEventTrigStmt *stmt);
+extern Oid CreateEventTrigger(CreateEventTrigStmt *stmt, bool is_internal);
extern Oid get_event_trigger_oid(const char *trigname, bool missing_ok);
extern Oid AlterEventTrigger(AlterEventTrigStmt *stmt);
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 70d5e3680a..a3fca9e64c 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -22,6 +22,13 @@
/* Same as MAXNUMMESSAGES in sinvaladt.c */
#define MAX_RELCACHE_INVAL_MSGS 4096
+typedef struct PublicationRelInfo
+{
+ Relation relation;
+ Node *whereClause;
+ List *columns;
+} PublicationRelInfo;
+
extern ObjectAddress CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt);
extern void AlterPublication(ParseState *pstate, AlterPublicationStmt *stmt);
extern void RemovePublicationById(Oid pubid);
@@ -35,5 +42,12 @@ extern bool pub_rf_contains_invalid_column(Oid pubid, Relation relation,
List *ancestors, bool pubviaroot);
extern bool pub_collist_contains_invalid_column(Oid pubid, Relation relation,
List *ancestors, bool pubviaroot);
+extern Bitmapset *pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols,
+ MemoryContext mcxt);
+extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
+ bool if_not_exists);
+extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
+ bool if_not_exists);
+extern bool is_publishable_relation(Relation rel);
#endif /* PUBLICATIONCMDS_H */
diff --git a/src/include/replication/ddlmessage.h b/src/include/replication/ddlmessage.h
new file mode 100644
index 0000000000..e530ea6d26
--- /dev/null
+++ b/src/include/replication/ddlmessage.h
@@ -0,0 +1,60 @@
+/*-------------------------------------------------------------------------
+ * ddlmessage.h
+ * Exports from replication/logical/ddlmessage.c
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/replication/ddlmessage.h
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_LOGICAL_DDL_MESSAGE_H
+#define PG_LOGICAL_DDL_MESSAGE_H
+
+#include "access/xlog.h"
+#include "access/xlogdefs.h"
+#include "access/xlogreader.h"
+#include "nodes/nodes.h"
+
+
+/*
+ * Support for keeping track of deparsed commands.
+ */
+typedef enum DeparsedCommandType
+{
+ DCT_ObjectCreate,
+ DCT_ObjectDrop,
+ DCT_SimpleCmd,
+ DCT_TableAlter,
+ DCT_TableDropEnd,
+ DCT_TableDropStart
+} DeparsedCommandType;
+
+/*
+ * Generic logical decoding DDL message wal record.
+ */
+typedef struct xl_logical_ddl_message
+{
+ Oid dbId; /* database Oid emitted from */
+ Size prefix_size; /* length of prefix including null terminator */
+ Oid relid; /* id of the table */
+ DeparsedCommandType cmdtype; /* type of sql command */
+ Size message_size; /* size of the message */
+
+ /*
+ * payload, including null-terminated prefix of length prefix_size
+ */
+ char message[FLEXIBLE_ARRAY_MEMBER];
+} xl_logical_ddl_message;
+
+#define SizeOfLogicalDDLMessage (offsetof(xl_logical_ddl_message, message))
+
+extern XLogRecPtr LogLogicalDDLMessage(const char *prefix, Oid relid, DeparsedCommandType cmdtype,
+ const char *ddl_message, size_t size);
+
+/* RMGR API*/
+#define XLOG_LOGICAL_DDL_MESSAGE 0x00
+void logicalddlmsg_redo(XLogReaderState *record);
+void logicalddlmsg_desc(StringInfo buf, XLogReaderState *record);
+const char *logicalddlmsg_identify(uint8 info);
+
+#endif
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 14fa921ab4..c9ac708d32 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -27,6 +27,7 @@ extern void heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
extern void xact_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
extern void standby_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+extern void logicalddl_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
diff --git a/src/include/replication/logicalproto.h b/src/include/replication/logicalproto.h
index 0ea2df5088..5fb3baaeb1 100644
--- a/src/include/replication/logicalproto.h
+++ b/src/include/replication/logicalproto.h
@@ -66,6 +66,7 @@ typedef enum LogicalRepMsgType
LOGICAL_REP_MSG_RELATION = 'R',
LOGICAL_REP_MSG_TYPE = 'Y',
LOGICAL_REP_MSG_MESSAGE = 'M',
+ LOGICAL_REP_MSG_DDL = 'L',
LOGICAL_REP_MSG_BEGIN_PREPARE = 'b',
LOGICAL_REP_MSG_PREPARE = 'P',
LOGICAL_REP_MSG_COMMIT_PREPARED = 'K',
@@ -246,6 +247,9 @@ extern List *logicalrep_read_truncate(StringInfo in,
bool *cascade, bool *restart_seqs);
extern void logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn,
bool transactional, const char *prefix, Size sz, const char *message);
+extern void logicalrep_write_ddl(StringInfo out, XLogRecPtr lsn,
+ const char *prefix, Size sz, const char *message);
+extern char *logicalrep_read_ddl(StringInfo in, XLogRecPtr *lsn, const char **prefix, Size *sz);
extern void logicalrep_write_rel(StringInfo out, TransactionId xid,
Relation rel, Bitmapset *columns);
extern LogicalRepRelation *logicalrep_read_rel(StringInfo in);
diff --git a/src/include/replication/output_plugin.h b/src/include/replication/output_plugin.h
index 3ac6729386..2ad96d374a 100644
--- a/src/include/replication/output_plugin.h
+++ b/src/include/replication/output_plugin.h
@@ -90,6 +90,18 @@ typedef void (*LogicalDecodeMessageCB) (struct LogicalDecodingContext *ctx,
Size message_size,
const char *message);
+/*
+ * Callback for the logical decoding DDL messages.
+ */
+typedef void (*LogicalDecodeDDLMessageCB) (struct LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid,
+ DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message);
+
/*
* Filter changes by origin.
*/
@@ -201,6 +213,19 @@ typedef void (*LogicalDecodeStreamMessageCB) (struct LogicalDecodingContext *ctx
Size message_size,
const char *message);
+/*
+ * Callback for streaming logical decoding DDL messages from in-progress
+ * transactions.
+ */
+typedef void (*LogicalDecodeStreamDDLMessageCB) (struct LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid,
+ DeparsedCommandType cmdtype,
+ Size message_size,
+ const char *message);
+
/*
* Callback for streaming truncates from in-progress transactions.
*/
@@ -221,6 +246,7 @@ typedef struct OutputPluginCallbacks
LogicalDecodeTruncateCB truncate_cb;
LogicalDecodeCommitCB commit_cb;
LogicalDecodeMessageCB message_cb;
+ LogicalDecodeDDLMessageCB ddl_cb;
LogicalDecodeFilterByOriginCB filter_by_origin_cb;
LogicalDecodeShutdownCB shutdown_cb;
@@ -239,6 +265,7 @@ typedef struct OutputPluginCallbacks
LogicalDecodeStreamCommitCB stream_commit_cb;
LogicalDecodeStreamChangeCB stream_change_cb;
LogicalDecodeStreamMessageCB stream_message_cb;
+ LogicalDecodeStreamDDLMessageCB stream_ddl_cb;
LogicalDecodeStreamTruncateCB stream_truncate_cb;
} OutputPluginCallbacks;
diff --git a/src/include/replication/pgoutput.h b/src/include/replication/pgoutput.h
index b4a8015403..a2cf99b4e4 100644
--- a/src/include/replication/pgoutput.h
+++ b/src/include/replication/pgoutput.h
@@ -25,6 +25,7 @@ typedef struct PGOutputData
uint32 protocol_version;
List *publication_names;
List *publications;
+ List *deleted_relids;
bool binary;
char streaming;
bool messages;
diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h
index 1b9db22acb..2b37c96c1f 100644
--- a/src/include/replication/reorderbuffer.h
+++ b/src/include/replication/reorderbuffer.h
@@ -11,6 +11,8 @@
#include "access/htup_details.h"
#include "lib/ilist.h"
+#include "nodes/nodes.h"
+#include "replication/ddlmessage.h"
#include "storage/sinval.h"
#include "utils/hsearch.h"
#include "utils/relcache.h"
@@ -66,6 +68,7 @@ typedef enum ReorderBufferChangeType
REORDER_BUFFER_CHANGE_UPDATE,
REORDER_BUFFER_CHANGE_DELETE,
REORDER_BUFFER_CHANGE_MESSAGE,
+ REORDER_BUFFER_CHANGE_DDL,
REORDER_BUFFER_CHANGE_INVALIDATION,
REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT,
REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID,
@@ -139,6 +142,16 @@ typedef struct ReorderBufferChange
char *message;
} msg;
+ /* DDL */
+ struct
+ {
+ char *prefix;
+ Size message_size;
+ char *message;
+ Oid relid;
+ DeparsedCommandType cmdtype;
+ } ddl;
+
/* New snapshot, set when action == *_INTERNAL_SNAPSHOT */
Snapshot snapshot;
@@ -470,6 +483,16 @@ typedef void (*ReorderBufferMessageCB) (ReorderBuffer *rb,
const char *prefix, Size sz,
const char *message);
+/* DDL message callback signature */
+typedef void (*ReorderBufferDDLMessageCB) (ReorderBuffer *rb,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid,
+ DeparsedCommandType cmdtype,
+ Size sz,
+ const char *message);
+
/* begin prepare callback signature */
typedef void (*ReorderBufferBeginPrepareCB) (ReorderBuffer *rb,
ReorderBufferTXN *txn);
@@ -536,6 +559,17 @@ typedef void (*ReorderBufferStreamMessageCB) (
const char *prefix, Size sz,
const char *message);
+/* stream DDL message callback signature */
+typedef void (*ReorderBufferStreamDDLMessageCB) (
+ ReorderBuffer *rb,
+ ReorderBufferTXN *txn,
+ XLogRecPtr message_lsn,
+ const char *prefix,
+ Oid relid,
+ DeparsedCommandType cmdtype,
+ Size sz,
+ const char *message);
+
/* stream truncate callback signature */
typedef void (*ReorderBufferStreamTruncateCB) (
ReorderBuffer *rb,
@@ -592,6 +626,7 @@ struct ReorderBuffer
ReorderBufferApplyTruncateCB apply_truncate;
ReorderBufferCommitCB commit;
ReorderBufferMessageCB message;
+ ReorderBufferDDLMessageCB ddl;
/*
* Callbacks to be called when streaming a transaction at prepare time.
@@ -611,6 +646,7 @@ struct ReorderBuffer
ReorderBufferStreamCommitCB stream_commit;
ReorderBufferStreamChangeCB stream_change;
ReorderBufferStreamMessageCB stream_message;
+ ReorderBufferStreamDDLMessageCB stream_ddl;
ReorderBufferStreamTruncateCB stream_truncate;
/*
@@ -696,6 +732,9 @@ extern void ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
Snapshot snap, XLogRecPtr lsn,
bool transactional, const char *prefix,
Size message_size, const char *message);
+extern void ReorderBufferQueueDDLMessage(ReorderBuffer *, TransactionId, XLogRecPtr lsn,
+ const char *prefix, Size message_size,
+ const char *message, Oid relid, DeparsedCommandType cmdtype);
extern void ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
TimestampTz commit_time, RepOriginId origin_id, XLogRecPtr origin_lsn);
diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h
index 1e7514dcff..32ccc156d0 100644
--- a/src/include/tcop/cmdtag.h
+++ b/src/include/tcop/cmdtag.h
@@ -16,7 +16,7 @@
/* buffer size required for command completion tags */
#define COMPLETION_TAG_BUFSIZE 64
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, ddl_repl_ok) \
tag,
typedef enum CommandTag
@@ -53,9 +53,11 @@ CopyQueryCompletion(QueryCompletion *dst, const QueryCompletion *src)
extern void InitializeQueryCompletion(QueryCompletion *qc);
extern const char *GetCommandTagName(CommandTag commandTag);
extern const char *GetCommandTagNameAndLen(CommandTag commandTag, Size *len);
+extern CommandTag *GetCommandTagsForDDLRepl(int *ncommands);
extern bool command_tag_display_rowcount(CommandTag commandTag);
extern bool command_tag_event_trigger_ok(CommandTag commandTag);
extern bool command_tag_table_rewrite_ok(CommandTag commandTag);
+extern bool command_tag_ddl_replication_ok(CommandTag commandTag);
extern CommandTag GetCommandTagEnum(const char *commandname);
extern Size BuildQueryCompletionString(char *buff, const QueryCompletion *qc,
bool nameonly);
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index e738ac1c09..7f44a150eb 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -23,196 +23,196 @@
* textual name, so that we can bsearch on it; see GetCommandTagEnum().
*/
-/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount */
-PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
-PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
-PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false)
-PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false)
-PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false)
-PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false)
-PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false)
-PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true)
-PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true)
-PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
-PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
-PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
-PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true)
-PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
-PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
-PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
-PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_MERGE, "MERGE", false, false, true)
-PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true)
-PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false)
-PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false)
-PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false)
-PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false)
-PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false)
-PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false)
-PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true)
-PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false)
-PG_CMDTAG(CMDTAG_SET, "SET", false, false, false)
-PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false)
-PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false)
-PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
-PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount, ddl_repl_ok */
+PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, true, false, true)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false, false)
+PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false, true)
+PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false, true)
+PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false, false)
+PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false, false)
+PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false, false)
+PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false, true)
+PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false, false)
+PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false, false)
+PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true, false)
+PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false, false)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false, true)
+PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false, false)
+PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true, false)
+PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false, false)
+PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false, false)
+PG_CMDTAG(CMDTAG_DO, "DO", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false, false)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false, true)
+PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false, false)
+PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false, false)
+PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true, false)
+PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false, true)
+PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false, true)
+PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true, false)
+PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false, false)
+PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false, false)
+PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_MERGE, "MERGE", false, false, true, false)
+PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true, false)
+PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false, false)
+PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false, false)
+PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false, false)
+PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false, false)
+PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false, true)
+PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false, false)
+PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false, false)
+PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false, false)
+PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false, true)
+PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false, false)
+PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false, false)
+PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false, false)
+PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false, true)
+PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false, false)
+PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false, false)
+PG_CMDTAG(CMDTAG_SET, "SET", false, false, false, false)
+PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false, false)
+PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false, false)
+PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false, false)
+PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false, false)
+PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false, false)
+PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true, false)
+PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false, false)
diff --git a/src/include/tcop/ddldeparse.h b/src/include/tcop/ddldeparse.h
index 8b369fd8c5..caefb03bf6 100644
--- a/src/include/tcop/ddldeparse.h
+++ b/src/include/tcop/ddldeparse.h
@@ -2,8 +2,7 @@
*
* ddldeparse.h
*
- * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
+ * Portions Copyright (c) 2023, PostgreSQL Global Development Group
*
* src/include/tcop/ddldeparse.h
*
@@ -14,9 +13,17 @@
#include "tcop/deparse_utility.h"
-extern char *deparse_utility_command(CollectedCommand *cmd);
+/* Context info needed for deparsing ddl command */
+typedef struct
+{
+ /* The maximum volatility of functions in expressions of a DDL command. */
+ char max_volatility;
+} ddl_deparse_context;
+
+extern char *deparse_utility_command(CollectedCommand *cmd,
+ ddl_deparse_context * context);
extern char *deparse_ddl_json_to_string(char *jsonb);
extern char *deparse_drop_table(const char *objidentity, const char *objecttype,
- Node *parsetree);
+ Node *parsetree);
#endif /* DDL_DEPARSE_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 1426a353cd..a656f5bd7c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -17,10 +17,12 @@
#include "access/tupdesc.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/objectaddress.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "catalog/pg_publication.h"
#include "nodes/bitmapset.h"
+#include "nodes/lockoptions.h"
#include "partitioning/partdefs.h"
#include "rewrite/prs2lock.h"
#include "storage/block.h"
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 8f93028363..008f610d11 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -6223,9 +6223,9 @@ List of schemas
(0 rows)
\dRp "no.such.publication"
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
-------+-------+------------+---------+---------+---------+-----------+----------
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+------+-------+------------+---------+---------+---------+------------+-----------+----------
(0 rows)
\dRs "no.such.subscription"
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 69dc6cfd85..6a5af2fd3f 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -30,20 +30,20 @@ ERROR: conflicting or redundant options
LINE 1: ...ub_xxx WITH (publish_via_partition_root = 'true', publish_vi...
^
\dRp
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f
- testpub_default | regress_publication_user | f | f | t | f | f | f
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | f
+ testpub_default | regress_publication_user | f | f | t | f | f | f | f
(2 rows)
ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
\dRp
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f
- testpub_default | regress_publication_user | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ testpib_ins_trunct | regress_publication_user | f | t | f | f | f | f | f
+ testpub_default | regress_publication_user | f | t | t | t | f | f | f
(2 rows)
--- adding tables
@@ -87,10 +87,10 @@ RESET client_min_messages;
-- should be able to add schema to 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable ADD TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"public.testpub_tbl1"
Tables from schemas:
@@ -99,20 +99,20 @@ Tables from schemas:
-- should be able to drop schema from 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable DROP TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"public.testpub_tbl1"
-- should be able to set schema to 'FOR TABLE' publication
ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test;
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test"
@@ -123,10 +123,10 @@ CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test;
CREATE PUBLICATION testpub_for_tbl_schema FOR TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
RESET client_min_messages;
\dRp+ testpub_for_tbl_schema
- Publication testpub_for_tbl_schema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_for_tbl_schema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"pub_test.testpub_nopk"
Tables from schemas:
@@ -144,10 +144,10 @@ LINE 1: ...CATION testpub_parsertst FOR TABLES IN SCHEMA foo, test.foo;
-- should be able to add a table of the same schema to the schema publication
ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"pub_test.testpub_nopk"
Tables from schemas:
@@ -156,10 +156,10 @@ Tables from schemas:
-- should be able to drop the table
ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test"
@@ -170,10 +170,10 @@ ERROR: relation "testpub_nopk" is not part of the publication
-- should be able to set table to schema publication
ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
\dRp+ testpub_forschema
- Publication testpub_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"pub_test.testpub_nopk"
@@ -195,10 +195,10 @@ Publications:
"testpub_foralltables"
\dRp+ testpub_foralltables
- Publication testpub_foralltables
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | t | t | t | f | f | f
+ Publication testpub_foralltables
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | t | t | t | f | f | f | f
(1 row)
DROP TABLE testpub_tbl2;
@@ -210,19 +210,19 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3;
CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3;
RESET client_min_messages;
\dRp+ testpub3
- Publication testpub3
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub3
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"public.testpub_tbl3"
"public.testpub_tbl3a"
\dRp+ testpub4
- Publication testpub4
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub4
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"public.testpub_tbl3"
@@ -243,10 +243,10 @@ UPDATE testpub_parted1 SET a = 1;
-- only parent is listed as being in publication, not the partition
ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted;
\dRp+ testpub_forparted
- Publication testpub_forparted
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"public.testpub_parted"
@@ -261,10 +261,10 @@ ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
UPDATE testpub_parted1 SET a = 1;
ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true);
\dRp+ testpub_forparted
- Publication testpub_forparted
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | t
+ Publication testpub_forparted
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | t
Tables:
"public.testpub_parted"
@@ -293,10 +293,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub5 FOR TABLE testpub_rf_tbl1, testpub_rf_tbl2 WHERE (c <> 'test' AND d < 5) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | f | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -309,10 +309,10 @@ Tables:
ALTER PUBLICATION testpub5 ADD TABLE testpub_rf_tbl3 WHERE (e > 1000 AND e < 2000);
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | f | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -328,10 +328,10 @@ Publications:
ALTER PUBLICATION testpub5 DROP TABLE testpub_rf_tbl2;
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | f | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl3" WHERE ((e > 1000) AND (e < 2000))
@@ -339,10 +339,10 @@ Tables:
-- remove testpub_rf_tbl1 and add testpub_rf_tbl3 again (another WHERE expression)
ALTER PUBLICATION testpub5 SET TABLE testpub_rf_tbl3 WHERE (e > 300 AND e < 500);
\dRp+ testpub5
- Publication testpub5
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub5
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | f | f | f | f | f
Tables:
"public.testpub_rf_tbl3" WHERE ((e > 300) AND (e < 500))
@@ -375,10 +375,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_syntax1 FOR TABLE testpub_rf_tbl1, ONLY testpub_rf_tbl3 WHERE (e < 999) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub_syntax1
- Publication testpub_syntax1
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub_syntax1
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | f | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"public.testpub_rf_tbl3" WHERE (e < 999)
@@ -388,10 +388,10 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub_syntax2 FOR TABLE testpub_rf_tbl1, testpub_rf_schema1.testpub_rf_tbl5 WHERE (h < 999) WITH (publish = 'insert');
RESET client_min_messages;
\dRp+ testpub_syntax2
- Publication testpub_syntax2
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | f | f
+ Publication testpub_syntax2
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | f | f | f | f | f
Tables:
"public.testpub_rf_tbl1"
"testpub_rf_schema1.testpub_rf_tbl5" WHERE (h < 999)
@@ -506,10 +506,10 @@ CREATE PUBLICATION testpub6 FOR TABLES IN SCHEMA testpub_rf_schema2;
ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpub_rf_schema2.testpub_rf_tbl6 WHERE (i < 99);
RESET client_min_messages;
\dRp+ testpub6
- Publication testpub6
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub6
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"testpub_rf_schema2.testpub_rf_tbl6" WHERE (i < 99)
Tables from schemas:
@@ -723,10 +723,10 @@ CREATE PUBLICATION testpub_table_ins WITH (publish = 'insert, truncate');
RESET client_min_messages;
ALTER PUBLICATION testpub_table_ins ADD TABLE testpub_tbl5 (a); -- ok
\dRp+ testpub_table_ins
- Publication testpub_table_ins
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | f | f | t | f
+ Publication testpub_table_ins
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | f | f | f | t | f
Tables:
"public.testpub_tbl5" (a)
@@ -900,10 +900,10 @@ CREATE TABLE testpub_tbl_both_filters (a int, b int, c int, PRIMARY KEY (a,c));
ALTER TABLE testpub_tbl_both_filters REPLICA IDENTITY USING INDEX testpub_tbl_both_filters_pkey;
ALTER PUBLICATION testpub_both_filters ADD TABLE testpub_tbl_both_filters (a,c) WHERE (c != 1);
\dRp+ testpub_both_filters
- Publication testpub_both_filters
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_both_filters
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"public.testpub_tbl_both_filters" (a, c) WHERE (c <> 1)
@@ -1108,10 +1108,10 @@ ERROR: relation "testpub_tbl1" is already member of publication "testpub_fortbl
CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1;
ERROR: publication "testpub_fortbl" already exists
\dRp+ testpub_fortbl
- Publication testpub_fortbl
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortbl
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -1149,10 +1149,10 @@ Publications:
"testpub_fortbl"
\dRp+ testpub_default
- Publication testpub_default
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | f | f
+ Publication testpub_default
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | f | f
Tables:
"pub_test.testpub_nopk"
"public.testpub_tbl1"
@@ -1230,10 +1230,10 @@ REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
DROP TABLE testpub_parted;
DROP TABLE testpub_tbl1;
\dRp+ testpub_default
- Publication testpub_default
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | f | f
+ Publication testpub_default
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | f | f
(1 row)
-- fail - must be owner of publication
@@ -1243,20 +1243,20 @@ ERROR: must be owner of publication testpub_default
RESET ROLE;
ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
\dRp testpub_foo
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
--------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpub_foo | regress_publication_user | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+-------------+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ testpub_foo | regress_publication_user | f | t | t | t | f | f | f
(1 row)
-- rename back to keep the rest simple
ALTER PUBLICATION testpub_foo RENAME TO testpub_default;
ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
\dRp testpub_default
- List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
------------------+---------------------------+------------+---------+---------+---------+-----------+----------
- testpub_default | regress_publication_user2 | f | t | t | t | f | f
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+-----------------+---------------------------+------------+---------+---------+---------+------------+-----------+----------
+ testpub_default | regress_publication_user2 | f | t | t | t | f | f | f
(1 row)
-- adding schemas and tables
@@ -1272,19 +1272,19 @@ CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub1_forschema FOR TABLES IN SCHEMA pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
CREATE PUBLICATION testpub2_forschema FOR TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1298,44 +1298,44 @@ CREATE PUBLICATION testpub6_forschema FOR TABLES IN SCHEMA "CURRENT_SCHEMA", CUR
CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
RESET client_min_messages;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"public"
\dRp+ testpub4_forschema
- Publication testpub4_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub4_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"CURRENT_SCHEMA"
\dRp+ testpub5_forschema
- Publication testpub5_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub5_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"CURRENT_SCHEMA"
"public"
\dRp+ testpub6_forschema
- Publication testpub6_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub6_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"CURRENT_SCHEMA"
"public"
\dRp+ testpub_fortable
- Publication testpub_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"CURRENT_SCHEMA.CURRENT_SCHEMA"
@@ -1369,10 +1369,10 @@ ERROR: schema "testpub_view" does not exist
-- dropping the schema should reflect the change in publication
DROP SCHEMA pub_test3;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1380,20 +1380,20 @@ Tables from schemas:
-- renaming the schema should reflect the change in publication
ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1_renamed"
"pub_test2"
ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
\dRp+ testpub2_forschema
- Publication testpub2_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub2_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1401,10 +1401,10 @@ Tables from schemas:
-- alter publication add schema
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1413,10 +1413,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1425,10 +1425,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test1;
ERROR: schema "pub_test1" is already member of publication "testpub1_forschema"
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1436,10 +1436,10 @@ Tables from schemas:
-- alter publication drop schema
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
@@ -1447,10 +1447,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
ERROR: tables from schema "pub_test2" are not part of the publication
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
@@ -1458,29 +1458,29 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
-- drop all schemas
ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
(1 row)
-- alter publication set multiple schema
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test2;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1489,10 +1489,10 @@ Tables from schemas:
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA non_existent_schema;
ERROR: schema "non_existent_schema" does not exist
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
"pub_test2"
@@ -1501,10 +1501,10 @@ Tables from schemas:
-- removing the duplicate schemas
ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test1;
\dRp+ testpub1_forschema
- Publication testpub1_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub1_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
@@ -1583,18 +1583,18 @@ SET client_min_messages = 'ERROR';
CREATE PUBLICATION testpub3_forschema;
RESET client_min_messages;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
(1 row)
ALTER PUBLICATION testpub3_forschema SET TABLES IN SCHEMA pub_test1;
\dRp+ testpub3_forschema
- Publication testpub3_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub3_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables from schemas:
"pub_test1"
@@ -1604,20 +1604,20 @@ CREATE PUBLICATION testpub_forschema_fortable FOR TABLES IN SCHEMA pub_test1, TA
CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, TABLES IN SCHEMA pub_test1;
RESET client_min_messages;
\dRp+ testpub_forschema_fortable
- Publication testpub_forschema_fortable
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_forschema_fortable
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"pub_test2.tbl1"
Tables from schemas:
"pub_test1"
\dRp+ testpub_fortable_forschema
- Publication testpub_fortable_forschema
- Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f | t | t | t | t | f
+ Publication testpub_fortable_forschema
+ Owner | All tables | Inserts | Updates | Deletes | Table DDLs | Truncates | Via root
+--------------------------+------------+---------+---------+---------+------------+-----------+----------
+ regress_publication_user | f | t | t | t | f | t | f
Tables:
"pub_test2.tbl1"
Tables from schemas:
diff --git a/src/test/subscription/meson.build b/src/test/subscription/meson.build
index bd673a9d68..7dfcc13899 100644
--- a/src/test/subscription/meson.build
+++ b/src/test/subscription/meson.build
@@ -40,6 +40,7 @@ tests += {
't/031_column_list.pl',
't/032_subscribe_use_index.pl',
't/033_run_as_table_owner.pl',
+ 't/034_ddl_replication.pl',
't/100_bugs.pl',
],
},
diff --git a/src/test/subscription/t/034_ddl_replication.pl b/src/test/subscription/t/034_ddl_replication.pl
new file mode 100644
index 0000000000..f239d69694
--- /dev/null
+++ b/src/test/subscription/t/034_ddl_replication.pl
@@ -0,0 +1,374 @@
+# Copyright (c) 2022, PostgreSQL Global Development Group
+# Regression tests for logical replication of DDLs
+#
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+my $ddl = "CREATE TABLE test_rep(id int primary key, name varchar, value integer);";
+$node_publisher->safe_psql('postgres', $ddl);
+$node_publisher->safe_psql('postgres', "INSERT INTO test_rep VALUES (1, 'data1', 1);");
+$node_subscriber->safe_psql('postgres', $ddl);
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION mypub FOR ALL TABLES with (publish = 'insert, update, delete', ddl = 'table');");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION mysub CONNECTION '$publisher_connstr' PUBLICATION mypub;");
+$node_publisher->wait_for_catchup('mysub');
+
+# Make sure we have fully synchronized the table.
+# This prevents ALTER TABLE command below from being executed during table synchronization.
+$node_subscriber->poll_query_until('postgres',
+ "SELECT COUNT(1) = 0 FROM pg_subscription_rel sr WHERE sr.srsubstate NOT IN ('s', 'r') AND sr.srrelid = 'test_rep'::regclass"
+);
+
+# Test ALTER TABLE ADD
+$node_publisher->safe_psql('postgres', "ALTER TABLE test_rep ADD c4 int;");
+$node_publisher->safe_psql('postgres', "INSERT INTO test_rep VALUES (2, 'data2', 2, 2);");
+$node_publisher->wait_for_catchup('mysub');
+my $result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM test_rep WHERE c4 = 2;");
+is($result, qq(1), 'ALTER test_rep ADD replicated');
+
+# Test ALTER TABLE DROP
+$node_publisher->safe_psql('postgres', "ALTER TABLE test_rep DROP c4;");
+$node_publisher->safe_psql('postgres', "DELETE FROM test_rep where id = 2;");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from test_rep;");
+is($result, qq(1), 'ALTER test_rep DROP replicated');
+
+# Test ALTER TABLE ALTER TYPE
+$node_publisher->safe_psql('postgres', "ALTER TABLE test_rep ALTER value TYPE varchar");
+$node_publisher->safe_psql('postgres', "INSERT INTO test_rep VALUES (3, 'data3', '3');");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM test_rep WHERE value = '3';");
+is($result, qq(1), 'ALTER test_rep ALTER COLUMN TYPE replicated');
+
+# Test ALTER TABLE ALTER SET DEFAULT
+# Check if we have the default value after the direct insert to subscriber node.
+$node_publisher->safe_psql('postgres', "ALTER TABLE test_rep ALTER COLUMN value SET DEFAULT 'foo'");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->safe_psql('postgres', "INSERT INTO test_rep VALUES (4, 'data4');");
+$result = $node_subscriber->safe_psql('postgres', "SELECT value FROM test_rep WHERE id = 4;");
+is($result, 'foo', 'ALTER test_rep ALTER SET DEFAULT replicated');
+
+# Test ALTER TABLE ALTER DROP DEFAULT
+# Check if we don't have the default value previously defined.
+$node_publisher->safe_psql('postgres', "ALTER TABLE test_rep ALTER COLUMN value DROP DEFAULT;");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->safe_psql('postgres', "INSERT INTO test_rep VALUES (5, 'data5');");
+$result = $node_subscriber->safe_psql('postgres', "SELECT value IS NULL FROM test_rep WHERE id = 5;");
+is($result, q(t), 'ALTER test_rep ALTER DROP DEFAULT replicated');
+
+# Test ALTER TABLE ALTER SET NOT NULL
+# Remove the existing record that contains null value first.
+my ($stdout, $stderr);
+$node_subscriber->safe_psql('postgres', "DELETE FROM test_rep WHERE id = 5;");
+$node_publisher->safe_psql('postgres', "ALTER TABLE test_rep ALTER value SET NOT NULL;");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "INSERT INTO test_rep VALUES (6, 'data6');",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: null value in column \"value\" of relation \"test_rep\" violates not-null constraint/
+ or die "failed to replicate ALTER TABLE ALTER SET NOT NULL";
+
+# Test ALTER TABLE ALTER DROP NOT NULL
+$node_publisher->safe_psql('postgres', "ALTER TABLE test_rep ALTER value DROP NOT NULL;");
+$node_publisher->wait_for_catchup('mysub');
+# Insert same data that has NULL value. This failed before but now should succeed.
+$node_subscriber->safe_psql('postgres', "INSERT INTO test_rep VALUES (6, 'data6');");
+$result = $node_subscriber->safe_psql('postgres', "SELECT value IS NULL FROM test_rep WHERE id = 6;");
+is($result, q(t), "ALTER test_rep ALTER DROP NOT NULL replicated");
+
+# Test ALTER TABLE SET UNLOGGED
+$node_publisher->safe_psql('postgres', 'ALTER TABLE test_rep SET UNLOGGED;');
+$node_publisher->wait_for_catchup('mysub');
+$node_publisher->safe_psql('postgres', "INSERT INTO test_rep VALUES (7, 'data7', '7');");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM test_rep WHERE id = 7;");
+is($result, qq(0), 'ALTER test_rep SET UNLOGGED replicated');
+
+# Test ALTER TABLE SET LOGGED
+$node_publisher->safe_psql('postgres', 'ALTER TABLE test_rep SET LOGGED;');
+$node_publisher->wait_for_catchup('mysub');
+$node_publisher->safe_psql('postgres', "INSERT INTO test_rep VALUES (8, 'data8', '8');");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM test_rep WHERE id = 8;");
+is($result, qq(1), 'ALTER test_rep SET LOGGED replicated');
+
+# Test CREATE TABLE and DML changes
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (a int, b varchar);");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from pg_tables where tablename = 'tmp';");
+is($result, qq(1), 'CREATE tmp is replicated');
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp values (1, 'a')");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp values (2, 'b')");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from tmp;");
+is($result, qq(2), 'DML Changes to tmp are replicated');
+
+# Test CREATE TABLE INHERITS
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp2 (c3 int) INHERITS (tmp);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp2 VALUES (1, 'a', 1);");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from pg_tables where tablename = 'tmp2';");
+is($result, qq(1), 'CREATE TABLE INHERITS is replicated');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from tmp2;");
+is($result, qq(1), 'inserting some data to inherited table replicated');
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp2");
+
+# Test CREATE TABLE LIKE
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp3 (c3 int, LIKE tmp);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp3 VALUES (1, 1, 'a');");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from pg_tables where tablename = 'tmp3';");
+is($result, qq(1), 'CREATE TABLE LIKE replicated');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from tmp3;");
+is($result, qq(1), 'insert some data to a table defined with LIKE replicated');
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp3");
+
+# Test DROP TABLE
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from pg_tables where tablename = 'tmp';");
+is($result, qq(0), 'TABLE tmp is dropped');
+
+# Test CREATE TABLE IF NOT EXISTS
+$node_publisher->safe_psql('postgres', "CREATE TABLE IF NOT EXISTS tmp (id int);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (1);");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from pg_tables where tablename = 'tmp';");
+is($result, qq(1), 'CREATE TABLE IF NOT EXISTS replicated');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from tmp;");
+is($result, qq(1), 'inserting data to a table replicated');
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp");
+
+# Test CREATE TABLE IF NOT EXISTS (check if we skip to create a table
+# when we have the table on the subscriber in advance, and if we succeed
+# in replicating changes.)
+$node_subscriber->safe_psql('postgres', "CREATE TABLE tmp (id int);");
+$node_publisher->safe_psql('postgres', "CREATE TABLE IF NOT EXISTS tmp (id int);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (1);");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from tmp;");
+is($result, qq(1), 'CREATE TABLE IF NOT EXISTS replicated');
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp");
+
+# Test CREATE TABLE IF NOT EXISTS (check if we skip to create a table
+# when we have the table on the publisher, but not on the subscriber.)
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int);");
+$node_publisher->safe_psql('postgres', "CREATE TABLE IF NOT EXISTS tmp (id int);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (1);");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from tmp;");
+is($result, qq(1), 'CREATE TABLE IF NOT EXISTS replicated');
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp");
+
+# Test CREATE TABLE with collate
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (name text COLLATE \"C\");");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES ('foo');");
+$node_publisher->wait_for_catchup('mysub');
+$result = $node_subscriber->safe_psql('postgres', "SELECT collation_name FROM information_schema.columns WHERE table_name = 'tmp';");
+is($result, qq(C), 'CREATE TABLE with collate replicated');
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp");
+
+# Test CREATE TABLE with named constraint
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int CONSTRAINT \"must be bigger than 10\" CHECK (id > 10));");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "INSERT INTO tmp VALUES (1);",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: new row for relation "tmp" violates check constraint "must be bigger than 10"/
+ or die "failed to replicate named constraint at creating table";
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+# Test CREATE TABLE with various types of constraints.
+# NOT NULL constraint
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int, name text NOT NULL);");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "INSERT INTO tmp VALUES (1);",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: null value in column "name" of relation "tmp" violates not-null constraint/
+ or die "failed to replicate non null constraint at creating table";
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+# NULL constraint
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int, name text NULL);");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->safe_psql('postgres', "INSERT INTO tmp VALUES (1);");
+$result = $node_subscriber->safe_psql('postgres', "SELECT name IS NULL FROM tmp;");
+is($result, qq(t), "CREATE TABLE with NULL constraint replicated");
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp");
+
+# CHECK constraint
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int, product_ame text, price int CHECK (price > 0));");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "INSERT INTO tmp VALUES (1, 'foo', -100);",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: new row for relation "tmp" violates check constraint "tmp_price_check"/
+ or die "failed to replicate CHECK constraint";
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+# DEFAULT
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int, name text DEFAULT 'foo');");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->safe_psql('postgres', "INSERT INTO tmp VALUES (1);");
+$result = $node_subscriber->safe_psql('postgres', "SELECT name from tmp;");
+is($result, qq(foo), "CREATE TABLE with default value replicated");
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp");
+
+# UNIQUE constraint
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int UNIQUE);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (1);");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "INSERT INTO tmp VALUES (1);",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: duplicate key value violates unique constraint "tmp_id_key"/
+ or die "failed to replicate constraint at creating table";
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+# PRIMARY KEY
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int PRIMARY KEY, name text);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (1, 'foo');");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "INSERT INTO tmp VALUES (1, 'bar');",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: duplicate key value violates unique constraint "tmp_pkey"/
+ or die "failed to replicate primary key at creating table";
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+# EXCLUDE
+$node_publisher->safe_psql('postgres', "CREATE TABLE circles (c circle, EXCLUDE USING gist (c WITH &&));");
+$node_publisher->safe_psql('postgres', "INSERT INTO circles VALUES ('<(1, 1), 1>'::circle);");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "INSERT INTO circles VALUES ('<(1, 1), 1>'::circle);",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: conflicting key value violates exclusion constraint "circles_c_excl"/
+ or die "failed to replicate EXCLUDE at creating table";
+$node_publisher->safe_psql('postgres', "DROP TABLE circles");
+
+# REFERENCES
+$node_publisher->safe_psql('postgres', "CREATE TABLE product (id int PRIMARY KEY, name text);");
+$node_publisher->safe_psql('postgres', "INSERT INTO product VALUES (1, 'foo');");
+$node_publisher->safe_psql('postgres', "INSERT INTO product VALUES (2, 'bar');");
+$node_publisher->safe_psql('postgres', "CREATE TABLE orders (order_id int PRIMARY KEY, product_id int REFERENCES product (id))");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "INSERT INTO orders VALUES (1, 10)",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_id_fkey"/
+ or die "failed to replicate REFERENCES at creating table";
+$node_publisher->safe_psql('postgres', "DROP TABLE orders");
+$node_publisher->safe_psql('postgres', "DROP TABLE product");
+
+# DEFERRABLE
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int PRIMARY KEY DEFERRABLE, name text);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (1, 'foo');");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (2, 'bar');");
+$node_publisher->wait_for_catchup('mysub');
+# Quick check of deferrable clause
+$node_subscriber->safe_psql('postgres', "UPDATE tmp SET id = id + 1;");
+# Also, execute a test that should fail for INITIALLY IMMEDIATE(the default)
+$node_subscriber->psql('postgres', qq(
+BEGIN;
+UPDATE tmp SET id = id + 1;
+INSERT INTO tmp VALUES (3, 'foobar');
+DELETE FROM tmp WHERE id = 3;
+COMMIT;
+), on_error_stop => 0, stderr => \$stderr, stdout => \$stdout);
+$stderr =~ /ERROR: duplicate key value violates unique constraint "tmp_pkey"/
+ or die "failed to replicate DEFERRABLE at creating table";
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+# NOT DEFERRABLE
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int PRIMARY KEY NOT DEFERRABLE, name text);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (1, 'foo');");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (2, 'bar');");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "UPDATE tmp SET id = id + 1;",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: duplicate key value violates unique constraint "tmp_pkey"/
+ or die "failed to replicate NOT DEFERRABLE at creating table";
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+# DEFERRABLE and INITIALLY DEFERRED
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int PRIMARY KEY DEFERRABLE INITIALLY DEFERRED, name text);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (1, 'foo');");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (2, 'bar');");
+$node_publisher->wait_for_catchup('mysub');
+# Quick check of deferrable clause
+$node_subscriber->safe_psql('postgres', "UPDATE tmp SET id = id + 1;");
+# Also, execute a test that should succeed for INITIALLY DEFERRED
+$node_subscriber->safe_psql('postgres', qq(
+BEGIN;
+UPDATE tmp SET id = id + 1;
+INSERT INTO tmp VALUES (3, 'foobar');
+DELETE FROM tmp WHERE id = 3;
+COMMIT;
+));
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+# Test CREATE TABLE with table constraint
+# We will set two checks and conduct two inserts that should fail respectively.
+$node_publisher->safe_psql('postgres',
+ "CREATE TABLE tmp (price int, discounted_price int, CHECK (discounted_price > 0 AND price > discounted_price));");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "INSERT INTO tmp VALUES (100, 0);",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: new row for relation "tmp" violates check constraint "tmp_check"/
+ or die "failed to replicate table constraint (first condition) at creating table";
+$node_subscriber->psql('postgres', "INSERT INTO tmp VALUES (50, 100);",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stderr =~ /ERROR: new row for relation "tmp" violates check constraint "tmp_check"/
+ or die "failed to replicate table constraint (second condition) at creating table";
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+# Test CREATE TABLE WITH strorage_parameter
+$node_publisher->safe_psql('postgres', "CREATE TABLE tmp (id int) WITH (fillfactor = 80);");
+$node_publisher->safe_psql('postgres', "INSERT INTO tmp VALUES (1);");
+$node_publisher->wait_for_catchup('mysub');
+$node_subscriber->psql('postgres', "SELECT reloptions FROM pg_class WHERE relname = 'tmp';",
+ on_error_stop => 0,
+ stderr => \$stderr,
+ stdout => \$stdout);
+$stdout =~ /{fillfactor=80}/
+ or die "failed to replicate storage option";
+$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) from tmp;");
+is($result, qq(1), 'CREATE TABLE with storage_parameter replicated');
+$node_publisher->safe_psql('postgres', "DROP TABLE tmp;");
+
+pass "DDL replication tests passed:";
+
+$node_subscriber->stop;
+$node_publisher->stop;
+
+done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7c8ae7fe7b..eb1f934ee3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -566,6 +566,7 @@ DefElemAction
DefaultACLInfo
DefineStmt
DeleteStmt
+DeparsedCommandType
DependencyGenerator
DependencyGeneratorData
DependencyType
@@ -1461,6 +1462,7 @@ LogicalDecodeBeginPrepareCB
LogicalDecodeChangeCB
LogicalDecodeCommitCB
LogicalDecodeCommitPreparedCB
+LogicalDecodeDDLMessageCB
LogicalDecodeFilterByOriginCB
LogicalDecodeFilterPrepareCB
LogicalDecodeMessageCB
@@ -1471,6 +1473,7 @@ LogicalDecodeStartupCB
LogicalDecodeStreamAbortCB
LogicalDecodeStreamChangeCB
LogicalDecodeStreamCommitCB
+LogicalDecodeStreamDDLMessageCB
LogicalDecodeStreamMessageCB
LogicalDecodeStreamPrepareCB
LogicalDecodeStreamStartCB
@@ -2314,6 +2317,7 @@ ReorderBufferChange
ReorderBufferChangeType
ReorderBufferCommitCB
ReorderBufferCommitPreparedCB
+ReorderBufferDDLMessageCB
ReorderBufferDiskChange
ReorderBufferIterTXNEntry
ReorderBufferIterTXNState
@@ -2323,6 +2327,7 @@ ReorderBufferRollbackPreparedCB
ReorderBufferStreamAbortCB
ReorderBufferStreamChangeCB
ReorderBufferStreamCommitCB
+ReorderBufferStreamDDLMessageCB
ReorderBufferStreamMessageCB
ReorderBufferStreamPrepareCB
ReorderBufferStreamStartCB
@@ -3916,6 +3921,7 @@ xl_heap_visible
xl_invalid_page
xl_invalid_page_key
xl_invalidations
+xl_logical_ddl_message
xl_logical_message
xl_multi_insert_tuple
xl_multixact_create
--
2.34.1
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: shveta.malik@gmail.com, amit.kapila16@gmail.com, michael@paquier.xyz, wangw.fnst@fujitsu.com, shiy.fnst@fujitsu.com, vignesh21@gmail.com, houzj.fnst@fujitsu.com, itsajin@gmail.com, runqidev@gmail.com, smithpb2250@gmail.com, tgl@sss.pgh.pa.us, ggysxcq@gmail.com, dilipbalaut@gmail.com, alvherre@alvh.no-ip.org, 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: <CAJpy0uDLLBYAOzCePYObZ51k1epBU0hef4vbfcujKJprJVsEcQ@mail.gmail.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